第3章 理解对象
3.1 定义属性
当一个属性第一次被添加给对象时,JavaScript在对象上调用一个名为[[Put]]的内部方法。这个操作不仅指定了初始的值,也定义了属性的一些特征。
当一个已有的属性被赋予一个新值时,调用的是一个名为[[Set]]的方法。
//Object实例化
var person1 = new Object();
person1.name = "liang"; //调用[[Put]]的内部方法
//对象字面形式
var person2 = {
name: "liang"
};
person2.name = "zhu"; //调用[[Set]]方法
3.2 属性探测
in 操作符在给定对象中查找一个给定名称的属性,如果找到则返回true。
in操作符会检测自有属性和原型属性,如果只要检测自有属性,使用
hasOwnProperty()方法。
var person1 = {
name: "liang",
age: 26
};
console.log("name" in person1); //true
console.log(person1.hasOwnProperty("name")); //true
//tostring是原型属性,所以用hasOwnProperty检测结果为false
console.log("toString" in person1); //true
console.log(person1.hasOwnProperty("toString")); //false
//可用这样一个函数来鉴别原型属性,结果为true则是原型属性
function check(object, name){
return (name in object) && !object.hasOwnProperty(name);
}
console.log(check(person1,"age")); //false
console.log(check(person1,"toString")); //true
3.3 删除属性
delete 操作符针对单个对象属性调用名为[[Delete]]的内部方法。操作成功时,它返回true。
var person1 = {
name: "liang",
age: 26
};
delete person1.age;
"age" in person1; //false
同名的自有属性会覆盖原型属性的值。delete 可以删除自有属性,不能删除原型属性。
3.4 属性枚举
[[Enumerable]]是属性的一个内部特征,指示属性是否可枚举,默认为true(可枚举的)。
for-in 循环会枚举一个对象所有的可枚举属性,并将属性赋给一个变量。
var person1 = {
name: "liang",
age: 26
};
var property;
for (property in person1){
console.log("Name" + ": " + property); //枚举属性
console.log("Value" + ": " + person1[property]); //枚举属性的值
}
//Name: name Value: liang Name: age Value: 26
Object.keys() 方法,获取可枚举属性的名字的数组。
for-in 循环会遍历原型属性,而Object.keys()只返回自有属性。
var person1 = {
name: "liang",
age: 26
};
//获取数组
var propertys = Object.keys(person1);
propertys; //["name", "age"]
//输出属性和属性值
var i, len;
for(i=0, len=propertys.length; i<len; i++){
console.log("Name" + ": " + propertys[i]);
console.log("Value" + ": " + person1[propertys[i]]);
} //Name: name Value: liang Name: age Value: 26
3.5 属性类型
对象的属性有两种类型:数据属性和访问器属性。
数据属性只包含一个值。
访问器属性不包含值,而是定义了一个当属性被读取时调用的函数(称为getter),和一个当属性被写入时调用的函数(称为setter)。
访问器属性仅需要getter或setter两者中的任意一个,也可以两者都有。
特殊关键字get和set被用在访问器属性名字的前面,后面跟着小括号和函数体。getter被期望返回一个值,而setter则接受一个需要被赋给属性的值作为参数。
var person1 = {
_name: "liang", //命名规范:下划线表示该属性被认为是私有的
get name(){
console.log("the name is ");
return this._name;
}, //此处加逗号
set name(value){
console.log("set name to",value);
this._name = value;
}
};
//注意:使用的是person1.name,而不是person1._name
console.log(person1.name); //the name is "liang"
console.log(person1._name); //liang
person1.name = "zhu"; //set name to zhu "zhu"
console.log(person1.name); //the name is "zhu"
console.log(Object.keys(person1)); //["_name", "name"]
console.log(Object.keys(person1)); //["_name", "name"]
通常情况下不使用访问器属性,但当你希望赋值操作会触发一些行为,或读取的值需要通过计算所需的返回值,可使用访问器属性。
如果只定义getter,该属性就变成只读。
如果只定义setter,该属性就变成只写。
3.6 属性特征
3.6.1 通用特征
有两个属性特征是数据和访问器属性都具有的。
[[Enumerable]],决定了是否可以遍历该属性。
[[Configurable]],决定了该属性是否可配置。delete可以删除可配置的属性。
Object.definedProperty() 方法,可用来改变属性特征。有3个参数:拥有该属性的对象、属性名、包含需要设置特征的属性描述对象。
var person1 = {
name: "liang"
};
Object.defineProperty(person1,"name",{
enumerable: false
});
console.log("name" in person1); //true
console.log(Object.keys(person1)); // []
3.6.2 数据属性特征
数据属性额外拥有两个特征:
[[Value]] 包含属性的值。
[[Writable]] 指示该属性是否可以写入。
var person1 = {};
Object.defineProperty(person1,"name",{
value: "liang",
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty() 方法调用时,会首先检查该属性是否存在,如果不存在,将根据属性描述对象指定的特征创建。调用这个方法,如果不指定特征的布尔值,它会默认设置为false。
3.6.2 访问器属性特征
访问器属性不需要存储值,因此没有[[Value]]和[[Writable]]。
有另外两个额外特征:[[Get]]和[[Set]]
之前的例子:
var person1 = {
_name: "liang", //命名规范:下划线表示该属性被认为是私有的,实际上还是公有的
get name(){
console.log("the name is ");
return this._name;
}, //此处加逗号
set name(value){
console.log("set name to",value);
this._name = value;
}
};
改写成如下形式:
var person1 = {
_name: "liang"
};
Object.defineProperty(person1,"name",{ //注意这里的name,不是_name,也可以用别的字符串来定义访问器属性
get:function(){
console.log("Reading name");
return this._name;
},
set:function(value){
console.log("Setting name to ",value);
this._name=value;
},
enumerable:true,
configurable:true
});
person1.name = "zhang"; //setting name to zhang "zhang"
3.6.4 定义多重属性
Object.defineProperties() 可以为一个对象同时定义多个属性。
接受两个参数:需要改变的对象、一个包含所有属性信息的对象。
var person1 = {};
Object.defineProperties(person1,{
_name: {
value: "liang",
enumerable: true,
configurable: true,
writable: true
},
name: {
get:function(){
console.log("reading name");
return this._name;
},
set: function(value){
console.log("setting name to %s",value);
this._name=value;
},
enumerable: true,
configurable: true
}
});
person1.name; //reading name "liang"
person1._name; //"liang"
3.6.5 获取属性特征
Object.getOwnPropertyDescriptor() 方法获取属性的特征。
这个方法只适用于自有属性。接受两个参数:对象、属性名。
var person1 = {
name: "liang"
};
var descriptor = Object.getOwnPropertyDescriptor(person,"name");
descriptor; //{value: "liang", writable: true, enumerable: true, configurable: true}
3.7 禁止修改对象
[[Extensible]] 指明该对象是否可以被修改。有3种方法来锁定对象属性。
Object.preventExtensible() 禁止扩展,对象不能继续添加新的属性。
Object.isExtensible() 判断是否为可扩展的。
Object.seal() 封印对象,对象不能继续添加新的属性,且不能删除和改变属性类型。 Object.isSealed() 判断是否被封印的。
Object.freeze() 冻结对象,对象不能继续添加新的属性,且不能删除和改变属性类型,还不能写入任何数据属性。 Object.isFrozen() 判断是否被冻结。
var person1 = {
name: "liang"
}
Object.seal(person1);
delete person1.name; //将无法删除
console.log(Object.isExtensible(person1)); //false
console.log(Object.isSealed(person1)); //true
第4章 构造函数和原型对象
4.1 构造函数
构造函数名的首字母要大写。
function Person(){
//statement
}
构造函数接受一个命名参数name并将其赋给this对象的name属性。
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("liang");
person1.sayName(); //"liang"
构造函数中还能用Object.defineProperty() 方法来帮助我们初始化。
function Person(name){
Object.defineProperty(this,"name",{
get:function(){
return name;
},
set:function(newName){
name = newName;
},
enumerable:true,
configurable:true
});
}
4.2 原型对象
几乎所有的函数(除了一些内建函数)都有一个名为prototype的属性,该属性是一个原型对象,用来创建新的对象实例。
所有创建的对象实例共享该原型对象,且这些对象实例可以访问原型对象的属性。
当你试图访问一个对象的某个属性时,JavaScript首先在自有属性里查找该名字,如果在自有属性里没有找到则查找原型属性。
4.2.1 [[Prototype]] 属性
对象实例通过内部属性[[Prototype]]跟踪其原型对象。该属性是对象实例指向原型对象的指针。
Object.getPrototypeOf() 方法读取[[Prototype]]属性的值。
var obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
//任何一个泛用对象,其[[Prototype]]属性始终指向Object.prototype
isPrototypeOf() 方法检测某个对象(Object)是否是另一个对象(obj)的原型对象。
var obj = {};
console.log(Object.prototype.isPrototypeOf(obj)); //true
或者说isPrototypeOf() 是检测一个对象(person1)是否是另一个对象(Person)的对象实例。
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("liang");
Person.prototype.isPrototypeOf(person1); //true
Object.getPrototypeOf(person1) === Person.prototype; //true
4.2.2 在构造函数中使用原型对象
function Person(name){
this.name = name;
}
console.log(Person.prototype); //{constructor: ƒ}
Person.prototype.sayName = function(){
return this.name;
};
console.log(Person.prototype); //{sayName: ƒ, constructor: ƒ}
var person1 = new Person("liang1");
person1.sayName(); //"liang1"
上例中,sayName() 现在是一个原型属性而不是自有属性。
如下的构造函数,sayName是一个自有属性,区分这两种创建方式。
function Person(name){
this.name = name;
this.sayName = function(){
return this.name;
}
}
console.log(Person.prototype); //{constructor: ƒ}
原型对象上存储其他类型的数据,存储引用值时要注意,这些引用值会被多个实例共享。
function Person(name){
this.name = name;
}
Person.prototype.favorites= [];
var person1 = new Person("liang");
var person2 = new Person("zhu");
person1.favorites.push("pizza");
person2.favorites.push("quinoa");
person1.favorites; //["pizza", "quinoa"]
通过一个对象字面形式替换原型对象,来设置多个原型属性。
function Person(name){
this.name = name;
}
Person.prototype={
sayName: function(){
return this.name;
},
toString: function(){
return "[Person " + this.name + "]";
}
};
var person1 = new Person("liang");
console.log(person1.sayName()); //"liang"
console.log(person1.toString()); //"[Person liang]"
使用对象字面形式改写原型对象改变了构造函数的属性,因此它现在指向Object而不是Person。
console.log(person1.constructor === Person); //false
console.log(person1.constructor === Object); //true
这是因为原型对象具有一个constructor属性,这是对象实例所没有的。当一个函数被创建时,它的prototype属性也被创建,且该原型对象的constructor属性指向该函数。当使用对象字面形式改写原型对象Person.prototype时,其contructor属性将被置为泛用对象Object。
为避免这一点,手动重置constructor属性:
function Person(name){
this.name = name;
}
Person.prototype={
constructor:Person,
sayName: function(){
return this.name;
},
toString: function(){
return "[Person " + this.name + "]";
}
};
构造函数、原型对象、对象实例,这三者的关系:
对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。
所有一切对象的原型顶端,都是Object.prototype。
4.2.3 改变原型对象
给定类型的所有对象实例共享一个原型对象。[[Prototype]]属性只是一个指向原型对象的指针,任何对原型对象的改变都立即反应到所有引用它的对象实例上。
使用Object.seal()或Object.freeze()方法,将无法添加自有属性或改变冻结对象的自有属性,但可以通过在原型对象上添加属性来扩展这些对象实例。
var person1 = new Person("liang");
var person2 = new Person("zhu");
Object.freeze(person1);
Person.prototype.sayHi = function(){
console.log("Hi");
};
person1.sayHi();
person2.sayHi();
4.2.4 内建对象的原型对象
所有内建对象都有构造函数,因此也都有原型对象可改变。
Array.prototype.sum = function(){
return this.reduce(function(previous,current){ //reduce()是数组方法
return previous + current;
});
};
var numbers = [1,2,3,4,5];
numbers.sum(); //15
在sum() 内部,this指向数组的对象实例numbers,于是该方法也可以自由使用数组的其他方法,比如reduce()。
内建对象的原型对象虽然可以改变,但不建议在生产环境中使用。可以用来做实验和验证新功能。