JS笔记—— 对象 (原型对象)

    JavaScript对象是一个很有意思的数据类型,由于js没有类的概念,js对象就承担起JavaScript面向对象的重任。
    JavaScript 并没有类的概念,但是它有对象。
    ECMSscript将对象定义为无序属性的集合,其属性可以包含基本值、对象或者函数。
    JavaScript的对象的属性没有严格的顺序,每个属性和方法都需有一个名字和对应的值,看起来和JSON基本没有区别(实际上js对象和JSON可相互转换)。

对象基本概念

对象的创建非常的简单>>>

显示申明
var obj = new Object()
obj.name = "tom"
obj.age = 21
obj.sayName = function(){return this.name}

**对象字面量**
var obj = {name:"tom",
            age:21,
            sayName:function(){return this.name}
        }

    ECMA-262第五版定义了内部采用的特性时(attribute), 描述了属性(property)
注:property是用来描述对象的,attribute是用来描述property的元数据,此处的attribute区别于HTML的attribute,HTML标签的attribute是标签本身所具有的描述数据,和property描述的对象(DOM)不同,也就是这两个的描述参照并不是同一基准

属性

    为了支持JavaScript引擎工作,ECMAScript5规定了属性的类型:数据属性访问器属性

数据属性

    数据属性:数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有四个描述其行为的特性。

[[Configurable]]:是否能够对属性进行其他更改,包括删除,修改等
[[Enumerable]]:是否能通过for-in循环返回属性
[[Writable]]:如字面意思,能否写入值(修改值)
[[Value]]:包含属性的数据值,代表属性值得存储位置

四个特性默认值为,true,true,true,undefined

    修改属性默认特性:Object.defineProperty(obj,propertyName,attrConfig)
    该方法接收三个参数:修改的对象,修改的属性值名称,该属性的特性描述符对象

例如:
var Car = {}
Object.defineProperty(Car,"price",{
    writeable:false,
    value:120000
})

注意:运用Object.defineProperty()产生的属性的特性默认为false,false,false,undefined
所以上面的例子中Car的price属性中,configurable默认为false,enumerable默认为false
直接通过对象创建的属性并无此限制

访问器属性

访问器属性就是我们在其他语言中常看到的getter和setter函数,访问器属性并不包含值,它也有四个特性

[[Configurable]]:是否能够对属性进行其他更改,包括删除,修改等
[[Enumerable]]:是否能通过for-in循环返回属性
[[Get]]:读取数据时调用的函数,默认为undefined
[[Set]]:写入数据时调用的函数,默认为undefined

例如:

var people = {
    name:"tom",
    _age:17,
    adult:false
}

Object.defineProperty(people,"age",{
    get:function(){
        return this._age
    },
    set:function(val){
        this._age = val
        if(val > 18){
            this.adult = true
        }
    }
})

定义多个属性
要想定义一个属性就调用一次definProperty方法会非常繁琐,ES5就存在方法defineProperties支持多属性定义,只是传入的参数有些许区别
例如:

Object.defineProperties("people",{
    name:{
        writable:false,
        value:"Amy"
    },
    _age:{
        get:function(){}
        set:function(){}
    }
})

读取属性的特性
使用ECMAScript5的Object.getOwnPropertyDescriptor(),可以取得给定属性的描述符
参数:包含待查属性的对象,待查属性的名称
例如:

var perple = {}
Object.defineProperty(perple,"name",{
    writable:false,
    value:"Jack"
})
var result = Object.getOwnPropertyDescriptor(people,"name")
console.log(result.value)
console.log(result.writable)

原型对象

    对象的创建往往是重复的,就好像创建一个学生对象并不能完成所有工作,如果牵扯到班级的概念,可能就要创建班级中所有学生的对象了,对象也有很多的重复性,比如一个班的人所在班级学校都是一样的,是共有的,为了简化代码,也就有了原型对象的概念。
注:对象创建中,每一个学生代表一个实例,这和其他OO(面向对象)语言的类具有相似的理念。

    我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途。就是可以包含那些由特定类型的实例共享的属性和方法。通过字面意思,prototype就是通过调用构造函数而创建的那个对象的原型对象。而原型对象也有一个指针constructor指向他所代表的构造函数。例如:Person.prototype.constructor == Person

    原型对象就像是一个班级的标签,实例就是班级的学生,不管这个班级来了多少个新学生,只要这个班级叫一年级,那么这个班级的学生就都是一年级学生。原型对象包含所有实例公共的属性为所有实例共享。
    创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫 [[Prototype]] 。虽然在脚本中没有标准的方式访问 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

function Person(){}
    Person.prototype.name="Nicholis"
    Person.prototype.age=29
    Person.prototype.job="Software Engineer"
    Person.prototype.sayName=function(){
        console.log(this.name)
}

var person1 = new Person()
person1 .sayName() // Nicholis
var person2 = new Person()
person2 .sayName() // Nicholis
console.log(person1 .sayName == person2 .sayName) // true

JS笔记—— 对象 (原型对象)_第1张图片
对象实例关系图

需要注意的几点:

  1. 构造函数的prototype指针指向该特定类型对象(Person)的原型对象,同时该对象的constructor指正指向该构造函数。
  2. 实例的[[prototype]]并非构造函数的prototype指针,在主流浏览器中的实际实现是_proto_指针,指向构造函数的原型对象。
  3. 实例与构造函数并无直接关系

原型对象属性和实例属性

原型对象属性说不定也有和实例属性冲突的时候,比如运动会的时候大多数人在开幕式的任务就是走队列喊口号,但是领头的人喊的口号和班里其他人很有可能不同。
领头:一年三班
同学:团结一心
领头一年三班
同学:共创佳绩

在JS中,发生了冲突该如何选择,和现实中也是相差无几的。

function Class13(){}
Class13.prototype.sentence= "永争第一";
Class13.prototype.say = function(){
    alert(this.sentence);
};
var student = new Class13(); // 大多数同学
var leader= new Class13();  // 领头人
leader.sentence= "一年三班";
alert(leader.say ()); //"一年三班" —— 来自实例
alert(student .say ()); //"永争第一" ——原型

    以上代码不难看出,student实例没创建实例属性,便输出了原型对象属性,而设置了实例属性的leader输出的是实例属性,领头人在作为同学的基础上口号是“永争第一”,但是新的任务让他产生了新的属性,屏蔽了之前的设置。
    当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为 null ,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。

更简单的原型语法

每一次都要书写xxx.prototype着实让人心烦,还好我们有更简便的方法——字面量

function Person(){}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

但是用字面量的方法hi重写整个原型对象,导致的直接后果就是constructor指针不再是默认指向构造函数,而是Object
解决办法一:在字面量中定义constructor

function Person(){}
Person.prototype = {
    constructor : Person,
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

问题:constructor默认数据属性enumerable会设置为true

解决办法二:通过Object.defineProperty()重新定义constructor

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型对象的问题

  1. 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
  2. 原型对象包含引用类型值的属性,会使得实例对该属性的操作产生连锁反应。
function Person(){
}
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

以上代码反映了引用类型在原型对象中的问题:当实例直接对引用类型进行方法操作时,将不会为实例创建同名实例属性,而是寻址,找到friends数组的地址,再进行push操作,这就导致了实例改变了原型对象的值,使得其他实例也会取得改变后的friends。


单纯的将引用类型进行赋值操作,那就不会有以上问题,因为这个过程中并没有寻址这个环节,而直接进行了实例属性申明和赋值。如下:

var person1 = new Person();
var person2 = new Person();
person1.friends=["hello"];
console.log(person1.friends); //"hello"
console.log(person2.friends); //"Shelby,Court,Van"
console.log(person1.friends === person2.friends); //false
JS笔记—— 对象 (原型对象)_第2张图片
未命名文件.png

相关方法

isPrototypeOf()
用途:判断实例与目标是否含有原型对象关系,即判断对象是否是某实例的原型对象

alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true

Object.getPrototypeOf()
用途:获取实例的原型对象

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"

hasOwnProperty()
用途: 检测一个属性是存在于实例中。

function Person(){}
Person.prototype.name = "Tom";
var person1 = new Person();
var person2 = new Person();
person2.name = "Amy"
alert(person1.hasOwnProperty("name")); //false
alert(person2.hasOwnProperty("name")); //true
alert(person2.hasOwnProperty("nam")); //false

注:该方法结合操作符 in 可判断属性来自实例还是原型
in操作符会判断给定属性是否能通过对象访问,如果能,返回true,不能,返回false
结合hasOwnProperty()就很好判断属性来源了

function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}
alert(hasPrototypeProperty(person1, "name")); //true
alert(hasPrototypeProperty(person2, "name")); //false

Object.keys()
用途:要取得对象上所有可枚举的实例属性。接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"

Object.getOwnPropertyNames()
用途:得到所有实例属性,无论它是否可枚举。

ar keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
// constructor 不可枚举

你可能感兴趣的:(JS笔记—— 对象 (原型对象))