JavaScript之对象属性

栏目: JavaScript · 发布时间: 6年前

内容简介:ECMAScript 5定义了一个名为Object.create()的方法,它创建一个新对象, 其中第一个参数是这个对象的原型。Object.create()提供第二个可选参数,用以对对象的属性进行进一步描述。Object.create()是一个静态函数,而不是提供给某个对象调用的方法。使用它的方法很简单,只须传入所需的原型对象即可:通过原型继承创建一个新对象

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赋值:

  1. 属性赋值首先会检查o中是否已有x属性;
  2. 如果o中 已有 x属性,则需先判定x属性是o继承的属性还是自有属性,从而进一步判定属性x是否为只读属性,如果o的原型链中存在该属性但不允许修改则会导致属性赋值失败;

    1. 如果o中已有属性x,但这个属性 不是 继承来的,那么这个赋值操作只是简单改变o的这个已有属性x的值,赋值成功。
    2. 如果o中已有属性x,但这个属性 继承而来的,属性x允许赋值操作,那么这个赋值操作只改变这个o的属性x的值,而 不会去修改原型链 ,赋值成功。
  3. 如果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)。

  1. 原型属性
    对象的原型属性是用来继承属性的,这个属性如此重要,以至于我们经常把“o的原型属性”直接叫做“o的原型”。
    原型属性是在实例对象创建之初就设置好的,通过对象直接量创建的对象使用0bject. prototype作为它们的原型。通过new创建的对象使用构造函数的prototype属性作为它们的原型。通过0bject.create() 创建的对象使用第一-个参数(也可以是null)作为它们的原型。
  2. 类属性 对象的类属性(class attribute) 是-一个字符串,用以表示对象的类型信息。ECMAScript3和ECMAScript 5都未提供设置这个属性的方法,并只有一种间接的方法可以查询它。默认的toString()方法(继承自0bject.prototype)返回了如下这种格式的字符串:[object class]
    因此,要想获得对象的类,可以调用对象的toString()方法,然后提取已返回字符串的第8个到倒数第二个位置之间的字符。
  3. 可拓展性
    对象的可扩展性用以表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的,宿主对象的可扩展性是由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/)
我是Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。

个人笔记,整理不易,感谢阅读、点赞和收藏。

文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Advanced Web Metrics with Google Analytics, 2nd Edition

Advanced Web Metrics with Google Analytics, 2nd Edition

Brian Clifton / Sybex / 2010-3-15 / USD 39.99

Valuable tips and tricks for using the latest version of Google Analytics Packed with insider tips and tricks, this how-to guide is fully revised to cover the latest version of Google Analytics and sh......一起来看看 《Advanced Web Metrics with Google Analytics, 2nd Edition》 这本书的介绍吧!

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具