共计 7680 个字符,预计需要花费 20 分钟才能阅读完成。
JavaScript 之对象属性
Object.create()继承
ECMAScript 5 定义了一个名为 Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型。Object.create()提供第二个可选参数,用以对对象的属性进行进一步描述。
Object.create()是一个静态函数,而不是提供给某个对象调用的方法。使用它的方法很简单,只须传入所需的原型对象即可:
var o1 = object.create({x:1, y:2}); // o1 继承了属性 x 和 y
inherit()函数继承
通过原型继承创建一个新对象
// inherit() 返回了一个继承自原型对象 p 的属性的新对象
// 这里使用 ECMAScript 5 中的 0bject. create()函数(如果存在的话)
// 如果不存在 0bject . create(), 则退化使用其他方法
function inherit(p) {if (p == nul1) throw TypeError(); // p 是一个对象,但不能是 null
if (Object . create) // 如果 bject. create()存在
return object .create(p); // 直接使用它
var t = typeof p; // 否则进行进 - - 步检测
if (t !== "object" & t !== "function") throw TypeError();
function f() {}; // 定义一个空构造函数
f.prototype = p; // 将其原型属性设置为 p
return new f(); // 使用 f()创建 p 的继承对象
}
对象继承后属性的创建、访问和修改
原型链:
假设要查询对象 o 的属性 x,如果 o 中不存在 x,那么将会继续在 o 的原型对象中查询属性 x。如果原型对象中也没有 x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查询,直到找到 x 或者查找到一个原型是 nul1 的对象为止。可以看到,对象的原型属性构成了一个“链”,通过这个“链”可以实现属性的继承。
实例
var o = {};
o.x = 1;
var p = inherit(o);
p.y = 2;
var q = inherit(p);
q.y = 3;
var s = q.toString();
q.x + q.y // =>3
继承对象的属性赋值:
假设给对象 o 的属性 x 赋值:
- 属性赋值首先会检查 o 中是否已有 x 属性;
-
如果 o 中 已有 x 属性,则需先判定 x 属性是 o 继承的属性还是自有属性,从而进一步判定属性 x 是否为只读属性,如果 o 的原型链中存在该属性但不允许修改则会导致属性赋值失败;
- 如果 o 中已有属性 x,但这个属性 不是 继承来的,那么这个赋值操作只是简单改变 o 的这个已有属性 x 的值,赋值成功。
- 如果 o 中已有属性 x,但这个属性 是继承而来的,属性 x 允许赋值操作,那么这个赋值操作只改变这个 o 的属性 x 的值,而 不会去修改原型链,赋值成功。
- 如果 o 中 不存在 属性 x(原型链中也没有已定义的属性 x),那么赋值操作会直接为 o 创建一个新的属性 x,赋值成功。
总结:属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性,不会影响到原型链。但有一个例外,如果 o 继承自属性 x,而这个属性是一个具有 setter 方法的 accessor 属性。
属性访问错误
属性访问并不总是返回或设置一个值。查询一个不存在的属性并不会报错,如果在对象 o 自身的属性或继承的属性中均未找到属性 x,属性访问表达式 o. x 返回 undefined。
这里假设 book 对象有属性“sub-title”,而没有属性“subtitle”
book.subtitle; // => undefined: 属性不存在
但是,如果对象不存在,那么试图查询这个不存在的对象的属性就会报错。null 和 undefined 值都没有属性,因此查询这些值的属性会报错,接上例:
// 抛出一个类型错误异常,undefined 没有 length 属性
var len = book.subtitle.length;
除非确定 book 和 book.subtitle 都是 (或在行为上) 对象,否则不能这样写表达式 book.subtitle. length,因为这样会报错,下面提供了两种避免出错的方法:
方法一
// 一种冗余但很易懂的方法
var len = undefined;
if (book) {if (book. subtitle) len = book. subtitle . length;
}
方法二
// 一种更简练的常用方法,获取 subtitle 的 length 属性或 undefined
var len = book && book. subtitle && book. subtitle. length;
这里利用了 && 操作符的短路特点。
删除属性
delete 运算符可以删除对象的属性。它的操作数应当是一个属性访问表达式。让人感到意外的是,delete 只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
delete book.author; // book 不再有属性 author
delete book["main title"]; // book 也不再有属性 "main title"
delete 运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象. 上删除它,而且这会影响到所有继承自这个原型的对象)。
举例:
a = {p: { x: 1} };
b = a.p;
delete a.p;
b.x //=>1, 执行这段代码之后 b.x 的值依然是 1
由于已经删除的属性的引用依然存在,因此在 JavaScript 的某些实现中,可能因为这种不严谨的代码而造成内存泄漏。所以在销毁对象的时候,要遍历属性中的属性,依次删除。
当 delete 表达式删除成功或没有任何副作用 (比如删除不存在的属性) 时,它返回 true。如果 delete 后不是一个属性访问表达式,delete 同样返回 true:
delete 不能删除那些可配置性为 false 的属性(尽管可以删除不可扩展对象的可配置属性)。某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性。在严格模式中,删除一个不可配置属性会报一个类型错误。
属性检测
1. isPrototypeOf()方法
检测一个对象是否是另一个对象的原型。或者说一个对象是否被包含在另一个对象的原型链中
实例一:```javascript
var p = {x:1}; // 定义一个原型对象
var o = Object.create(p); // 使用这个原型创建一个对象
p.isPrototypeOf(o); //=>true:o 继承 p
Object.prototype.isPrototypeOf(p); //=> true, p 继承自 Object.prototype
```
实例二:```javascript
function Animal(){this.species = "动物";};
var eh = new Animal();
Animal.prototype.isPrototypeOf(eh) //=>true
```
综合上面的两个例子,我们发现在调用 isPrototypeOf()的时候有三种方式
```javascript
p.isPrototypeOf(o); //=>true
Object.prototype.isPrototypeOf(p);
Animal.prototype.isPrototypeOf(eh) //=>true
```
总结一下就是:
通过 Object.create()创建的对象使用第一个参数作为原型
通过对象直接量的对象使用 Object.prototype 作为原型
通过 new 创建的对象使用构造函数的 prototype 属性作为原型
2. instanceof 运算符
Instanceof 运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧对象是右侧类的实例,则表达式返回为 true,否则返回 false。
javascript
var d = new Date();
d instanceof Date; //=>true d 是 Date 的实例
d instanceof Object; //=>true 所有对象都是 Object 的实
当通过 instanceof 判断一个对象是否是一个类的实例的时候,这个判断也会包含对父类的检测。尽管 instanceof 的右操作数是构造函数,但计算过程实际是检测了对象的继承关系。
instanceOf 与 isPrototypeOf 简单总结
var d = new Date();
Date.prototype.isPrototypeOf(d); //=>true
d instanceof Date; //=>true
* 如果 Date.prototype 是 d 的原型,那么 d 一定是 Date 的实例。* 缺点为无法同对象来获得类型,只能检测对象是否属于类名
* 在多窗口和多框架的子页面的 web 应用中兼容性不佳。其中一个典型代表就是 instanceof 操作符不能视为一个可靠的数组检测方法。
3. hasOwnProperty()方法
对象的 hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回 false:
var o = {x: 1}
o.hasownProperty("x"); // true: o 有一个自有属性 x
o.hasOwnProperty("y"); // false: o 中不存在属性 y
o.hasOwnProperty("toString"); // false: toString 是继承属性
4. in 操作符
In 运算符左侧是属性名,右侧是对象,如果对象的自有属性或者继承属性中包含这个属性则返回 true。
var o = {x = 1}
"x" in o; // =>true
"y" in o; // =>false
"toString" in o; // =>true: toString 是继承属性
5. propertyIsEnumberable()方法
只有检测到是自有属性且这个属性可枚举(enumberable attribute)为 true 时它才返回 true。某些内置属性是不能枚举的。
var 0 = inherit({y: 2});
o.x = 1;
o. propertyIsEnumerable("x"); // true: o 有一个可枚举的自有属性 x
o. propertyIsEnumerable("y"); // false: y 是继承来的
Object. prototype . propertyIsEnumerable("toString"); // false: 不可枚举
枚举属性
1. for/in 循环
可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的(除非用下文中提到的一个方法将它们转换为不可枚举的)。例如:
var 0 = {x:1, y:2, z:3}; // 三个可枚举的自有属性
o.propertyIsEnumerable("toString") // =>false, 不可枚举
for(p in o) // 遍历属性
console.log(p); // 输出 x、y 和 z,不会输出 toString
有许多实用工具库给 0bject.prototype 添加了新的方法或属性,这些方法和属性可以被所有对象继承并使用。然而在 ECMAScript 5 标准之前,这些新添加的方法是不能定义为不可枚举的,因此它们都可以在 for/i n 循环中枚举出来。为了避免这种情况,需要过滤 for/in 循环返回的属性,下面两种方式是最常见的:
for(p in o) {if (!o. hasOwnProperty(p)) continue; // 跳过继承的属性
}
for(p in o) {if (typeof o[p] === "function") continue; // 跳过方法
}
2. Object.getOwnPropertyNames()方法
返回一个由指定对象的所有 自身 属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
// 类数组对象
var obj = {0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
// 使用 Array.forEach 输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {console.log(val + "->" + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c
// 不可枚举属性
var my_obj = Object.create({}, {
getFoo: {value: function() {return this.foo;},
enumerable: false
}
});
my_obj.foo = 1;
console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]
3. Object.keys()方法
返回一个由一个给定对象的 自身 可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致。如果对象的键 -gs 值都不可枚举,那么将返回由键组成的数组。
// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// array like object
var obj = {0: 'a', 1: 'b', 2: 'c'};
console.log(Object.keys(obj)); // console: ['0', '1', '2']
// array like object with random key ordering
var anObj = {100: 'a', 2: 'b', 7: 'c'};
console.log(Object.keys(anObj)); // console: ['2', '7', '100']
// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
getFoo: {value: function () {return this.foo;}
}
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']
属性的特性
我们将存取器属性的 getter 和 setter 方法看成是属性的特性。按照这个逻辑, 我们也可以把数据属性的值同样看做属性的特性。因此,可以认为一个属性包含一个名字和 4 个特性。数据属性的 4 个特性分别是它的 ** 值(value)**、** 可写性(writable)**、** 可枚举性(enumerable)** 和 ** 可配置性(configurable)**。存取器属性不具有值(value) 特性和可写性,它们的可写性是由 setter 方法存在与否决定的。因此存取器属性的 4 个特性是读取(get)、写入(set)、可枚举性和可配置性。为了实现属性特性的查询和设置操作,ECMAScript 5 中定义了一个名为“属性描述符”(property descriptor)的对象,这个对象代表那 4 个特性。描述符对象的属性和它们所描述的属性特性是同名的。因此,数据属性的描述符对象的属性有 value、writable.enumerable 和 configurable。存取器属性的描述符对象则用 get 属性和 set 属性代替 value 和 writable。其中 writable、enumerable 和 configurable 都是布尔值,当然,get 属性和 set 属性是函数值。
对象的三个属性
每一个对象都有与之相关的原型(prototype)、类(class) 和可扩展性(extensibleattribute)。
-
原型属性
对象的原型属性是用来继承属性的,这个属性如此重要,以至于我们经常把“o 的原型属性”直接叫做“o 的原型”。
原型属性是在实例对象创建之初就设置好的,通过对象直接量创建的对象使用 0bject. prototype 作为它们的原型。通过 new 创建的对象使用构造函数的 prototype 属性作为它们的原型。通过 0bject.create() 创建的对象使用第一 - 个参数 (也可以是 null) 作为它们的原型。 -
类属性
对象的类属性 (class attribute) 是 - 一个字符串,用以表示对象的类型信息。ECMAScript3 和 ECMAScript 5 都未提供设置这个属性的方法,并只有一种间接的方法可以查询它。默认的 toString() 方法 (继承自 0bject.prototype) 返回了如下这种格式的字符串:[object class]
因此,要想获得对象的类,可以调用对象的 toString()方法,然后提取已返回字符串的第 8 个到倒数第二个位置之间的字符。 -
可拓展性
对象的可扩展性用以表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的,宿主对象的可扩展性是由 JavaScript 引擎定义的。在 ECMAScript 5 中,所有的内置对象和自定义对象都是可扩展的,除非将它们转换为不可扩展的,同样,宿主对象的可扩展性也是由实现 ECMAScript 5 的 JavaScript 引擎定义的。
序列化对象
对象序列化 (serialization) 是指将对象的状态转换为字符串,也可将字符串还原为对象。ECMAScript 5 提供了内置函数 JSON.stringify() 和 JSON.parse()用来序列化和还原 JavaScript 对象。这些方法都使用 JSON 作为数据交换格式,JSON 的全称是“JavaScript Object Notation” 一 JavaScript 对象表示法,它的语法和 JavaScript 对象与数组直接量的语法非常相近:
o = {x:1, y:{z:[false, null, ""]}}; // 定义一个测试对象
s = JSON.stringify(o); // s 是 '{"x":1,"y":{"z":[false, null,""]}}'
p = JSON.parse(s); // p 是 o 的深拷贝
参考:
*《JavaScript 权威指南》第六版
* [MDN Web 文档](https://developer.mozilla.org/zh-CN/)
推荐阅读:
【专题:JavaScript 进阶之路】
JavaScript 之“use strict”
JavaScript 之 new 运算符
JavaScript 之 call() 理解
我是 Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!