资讯专栏INFORMATION COLUMN

JavaScript之对象属性

gekylin / 2087人阅读

摘要:尽管的右操作数是构造函数,但计算过程实际是检测了对象的继承关系。通过创建的对象使用构造函数的属性作为它们的原型。

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()方法
检测一个对象是否是另一个对象的原型。或者说一个对象是否被包含在另一个对象的原型链中

实例一:

var p = {x:1};                        //定义一个原型对象
var o = Object.create(p);                //使用这个原型创建一个对象
p.isPrototypeOf(o);                    //=>true:o继承p
Object.prototype.isPrototypeOf(p);    //=> true, p继承自Object.prototype

实例二:

function Animal(){
    this.species = "动物";
};
var eh = new Animal();
Animal.prototype.isPrototypeOf(eh)    //=>true

综合上面的两个例子,我们发现在调用isPrototypeOf()的时候有三种方式

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 o = 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 o = {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) 和可扩展性(extensible attribute)。

原型属性
对象的原型属性是用来继承属性的,这个属性如此重要,以至于我们经常把“o的原型属性”直接叫做“o的原型”。
原型属性是在实例对象创建之初就设置好的,通过对象直接量创建的对象使用Object. prototype作为它们的原型。通过new创建的对象使用构造函数的prototype属性作为它们的原型。通过Object.create() 创建的对象使用第一-个参数(也可以是null)作为它们的原型。

类属性
对象的类属性(class attribute) 是-一个字符串,用以表示对象的类型信息。ECMAScript3和ECMAScript 5都未提供设置这个属性的方法,并只有一种间接的方法可以查询它。默认的toString()方法(继承自Object.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,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。 
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105326.html

相关文章

  • JavaScript 闯关记

    摘要:对象数组初始化表达式,闯关记之上文档对象模型是针对和文档的一个。闯关记之数组数组是值的有序集合。数组是动态的,根闯关记之语法的语法大量借鉴了及其他类语言如和的语法。 《JavaScript 闯关记》之 DOM(下) Element 类型 除了 Document 类型之外,Element 类型就要算是 Web 编程中最常用的类型了。Element 类型用于表现 XML 或 HTML 元素...

    mj 评论0 收藏0
  • JavaScript深入变量对象

    摘要:深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象。下一篇文章深入之作用域链本文相关链接深入之执行上下文栈深入系列深入系列目录地址。 JavaScript深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象。全局上下文下的变量对象是什么?函数上下文下的活动对象是如何分析和执行的?还有两个思考题帮你加深印象,快来看看吧! 前言 在上篇《JavaScript深入之执行上下文栈》中...

    Zachary 评论0 收藏0
  • JavaScript深入执行上下文

    摘要:深入系列第七篇,结合之前所讲的四篇文章,以权威指南的为例,具体讲解当函数执行的时候,执行上下文栈变量对象作用域链是如何变化的。前言在深入之执行上下文栈中讲到,当代码执行一段可执行代码时,会创建对应的执行上下文。 JavaScript深入系列第七篇,结合之前所讲的四篇文章,以权威指南的demo为例,具体讲解当函数执行的时候,执行上下文栈、变量对象、作用域链是如何变化的。 前言 在《Jav...

    gougoujiang 评论0 收藏0
  • javascript 面向对象版块定义多个对象属性以及读取属性特性

    摘要:返回值是一个对象,如果是访问器属性,这个对象的属性有和如果是数据属性,这个对象的属性有和。上一篇面向对象版块之对象属性下一篇面向对象版块之创建对象 这是 javascript 面向对象版块的第三篇文章,主要讲解的是多个属性的定义以及读取属性的特性。前面这几章内容目的在于加深对对象的理解,这样可以利于理解后面的原型链以及继承方面的知识,或者你也可以了解一下不一样的 javascript ...

    wendux 评论0 收藏0
  • JavaScript深入作用域链

    摘要:下面,让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。这时候执行上下文的作用域链,我们命名为至此,作用域链创建完毕。 JavaScript深入系列第五篇,讲述作用链的创建过程,最后结合着变量对象,执行上下文栈,让我们一起捋一捋函数创建和执行的过程中到底发生了什么? 前言 在《JavaScript深入之执行上下文栈》中讲到,当JavaScript代码执行一段可执行代...

    waltr 评论0 收藏0
  • JavaScript深入从原型到原型链

    摘要:深入系列的第一篇,从原型与原型链开始讲起,如果你想知道构造函数的实例的原型,原型的原型,原型的原型的原型是什么,就来看看这篇文章吧。让我们用一张图表示构造函数和实例原型之间的关系在这张图中我们用表示实例原型。 JavaScript深入系列的第一篇,从原型与原型链开始讲起,如果你想知道构造函数的实例的原型,原型的原型,原型的原型的原型是什么,就来看看这篇文章吧。 构造函数创建对象 我们先...

    Songlcy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<