重读犀牛-对象札记

这是我参与 11 月更文挑战的第 18 天,活动详情查看:2021 最后一次更文挑战

对象简介

“原型式继承”(prototypal inheritance)是JavaScript的核心特征。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate)它的属性。

属性名可以包含空字符串在内的任意字符串。

“属性特性”(property attribute)

  • 可写(writable attribute),表明是否可以设置该属性的值。
  • 可枚举(enumerable attribute),表明是否可以通过for/in循环返回该属性。
  • 可配置(configurable attribute),表明是否可以删除或修改该属性。

对象特性(object attribute)

  • 对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象。
  • 对象的类(class)是一个标识对象类型的字符串。
  • 对象的扩展标记(extensible flag)指明了(在ES5中)是否可以向该对象添加新属性。

相关术语

  • 内置对象(native object)是由ECMAScript规范定义的对象或类。例如,数组、函数、日期和正则表达式都是内置对象。

  • 宿主对象(hosTobject)是由JavaScript解释器所嵌入的宿主环境(比如Web浏览器)定义的。客户端JavaScript中表示网页结构的HTMLElement对象均是宿主对象。既然宿主环境定义的方法可以当成普通的JavaScript函数对象,那么宿主对象也可以当成内置对象。

  • 自定义对象(user-defined object)是由运行中的JavaScript代码创建的对象。

  • 自有属性(own property)是直接在对象中定义的属性。

  • 继承属性(inherited property)是在对象的原型对象中定义的属性。

创建对象(create)

以通过对象直接量、关键字new和(ES5中的)Object.create()函数来创建对象

对象直接量 对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。如果值存在计算,那么多次调用中属性值可能不同。
最后一个属性后逗号将忽略。

关键字new new 后跟跟随一个构造函数(constructor),初始化一个新创建的对象。

原型链”(prototype chain)

  • 所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过Object.prototype获得对原型对象的引用。
  • 通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。也继承自Object.prototype。通过new Array()创建的对象的原型就是Array.prototype,通过new Date()创建的对象的原型就是Date.prototype。
  • Object.prototype没有原型的对象。它不继承任何属性。

其他原型对象都是普通对象,普通对象都具有原型。

  • 所有的内置构造函数(以及大部分自定义的构造函数)都具有一个继承自Object.prototype的原型。例如,Date.prototype的属性继承自Object.prototype,因此由new Date()创建的Date对象的属性同时继承自Date.prototype和Object.prototype。
  • null没原型

Object.create() Object.create()是一个静态函数,而不是提供给某个对象调用的方法。

var o1 = Object.create({x:1, y:2}); // o1继承了属性x和y

//创建一个没有原型的新对象
var o2 = Object.create(null); //o2不继承任何属性和方法

//创建一个普通的空对象
var o3 = Object.create(Object.prototype); //o3和{}和new Object()一样
复制代码

inherit()防止库函数无意间修不受控制的对象。修改继承对象的属性值,只会影响继承对象自身。

// inherit() 返回了一个继承自原型对象p的属性的新对象
// 这里使用ECMAScript 5中的Object.create()函数(如果存在的话)
// 如果不存在Object.create(),则退化使用其他方法
function inherit(p) {    
    if (p == null) throw TypeError(); // p是一个对象,但不能是null   
    if (Object.create) // 如果Object.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的继承对象
}
复制代码

属性的查询和设置

数组元素是通过字符串索引而不是数字索引。这种数组就是我们所说的关联数组(associative array),也称做散列、映射或字典(dictionary)。JavaScript对象都是关联数组。

“自有属性”(own property) 只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关 (override)

  • 属性赋值操作首先检查原型链,以此判定是否允许赋值操作。
  • 如果允许属性赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值,而不会去修改原型链。

null和undefined值都没有属性,查询这些值的属性会报错、设置属性也会报类型错误。

var len = book &&book.subtitle &&book.subtitle.length;
复制代码

对象设置属性失败场景

  • o中的属性p是只读的:不能给只读属性重新赋值(defineProperty()方法中有一个例外,可以对可配置的只读属性重新赋值)。
  • o中的属性p是继承属性,且它是只读的:不能通过同名自有属性覆盖只读的继承属性。
  • o中不存在自有属性p:o没有使用setter方法继承属性p,并且o的可扩展性(extensible attribute)是false。如果o中不存在p,而且没有setter方法可供调用,则p一定会添加至o中。但如果o不是可扩展的,那么在o中不能定义新属性。

删除

delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性
delete运算符只能删除自有属性,不能删除继承属性。

a={p:{x:1}};b=a.p;delete a.p;
b.x   // =>  1   已删除的引用依然存在
复制代码

当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete后不是一个属性访问表达式,delete同样返回true:

o = {x:1}; // o有一个属性x,并继承属性toString
delete o.x; // 删除x,返回true
delete o.x; // 什么都没做(x已经不存在了),返回true
delete o.toString; // 什么也没做(toString是继承来的),返回true
delete 1; // 无意义,返回true
```js
delete不能删除那些可配置性为false的属性,例如通过变量声明、函数声明创建的全局对象属性。     
当在非严格模式中删除全局对象的可配值属性时,可以省略对全局对象的引用,直接在delete操作符后跟随要删除的属性名即可
```js
this.x = 1; // 创建一个可配置的全局属性(没有用var)
delete x; // 将它删除
复制代码

检测属性

判断某个属性是否存在于某个对象中

  • in运算符 如果对象的自有属性或继承属性中包含这个属性则返回true:

  • hasOwnPreperty() 是否是对象的自有属性。对于继承属性它将返回false:

  • propertyIsEnumerable() 只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为true时它才返回true。通常由JavaScript代码创建的属性都是可枚举的

  • 通过属性查询 使用“!==”判断一个属性是否是undefined

==in可以区分不存在的属性和存在但值为undefined的属性。==

var o = { x: undefined } // 属性被显式赋值为undefined
o.x !== undefined // false:属性存在,但值为undefined
o.y !== undefined // false:属性不存在
"x" in o // true:属性存在
"y" in o // false:属性不存在
delete o.x; // 删除了属性x
"x" in o // false:属性不再存在


// 如果o中含有属性x,且x的值不是null或undefined,o.x乘以2.
if (o.x != null) o.x *= 2;

// 如果o中含有属性x,且x的值不能转换为false,o.x乘以2.
// 如果x是undefined、null、false、" " 、0或NaN,则它保持不变
if (o.x) o.x *= 2;
复制代码

枚举属性

for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性)。
对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的。

for (p in o) {    
    if (!o.hasOwnProperty(p)) continue; // 跳过继承的属性
}
for (p in o) {
    if (typeof o[p] === "function") continue; // 跳过方法
}
复制代码

Object.keys(),它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。
Object.getOwnPropertyNames(),返回对象的所有自有属性的名称,包括不可枚举.

getter setter

由getter和setter定义的属性称做“存取器属性”(accessor property),它不同于“数据属性”(data property),数据属性只有一个简单的值。
存取器属性不具有可写性(writable attribute).如果它只有setter方法,那么它是一个只写属性,读取只写属性总是返回undefined。存取器属性是可以继承的。

// 返回随机数的存取器属性 例如,表达式"random.octet"产生一个随机数
// 每次产生的随机数都在0~255之间
var random = {
    get octet() {
        return Math.floor(Math.random() * 256);
    },
    get uint16() {
        return Math.floor(Math.random() * 65536);
    },
    get int16() {
        return Math.floor(Math.random() * 65536) - 32768;
    }
};
复制代码

属性的特性

  • 给原型对象添加方法,并将它们设置成不可枚举的,这让它们看起来更像内置方法。
  • 给对象定义不能修改或删除的属性,借此“锁定”这个对象。

数据属性的4个特性分别是它的值(value)、可写性(writable)、可枚举性(enumerable)和可配置性(configurable)。
存取器属性的4个特性是读取(get)、写入(set)、可枚举性和可配置性。

“属性描述符”(property descriptor)的对象

  • 数据属性的描述符对象的属性有value、writable、enumerable和configurable。
  • 存取器属性的描述符对象的属性有get、set、enumerable和configurable。
  • 其中writable、enumerable和configurable都是布尔值,get属性和set属性是函数值

Object.getOwnPropertyDescriptor() 获得自有属性的描述符。对于继承属性和不存在的属性,返回undefined。
要想获得继承属性的特性,需要遍历原型链 Object.getPrototypeOf()。

设置属性的特性 Object.defineProperty() Object.defineProperties()

//新创建的属性来说,默认的特性值是false或undefined
var o = {};
Object.defineProperty(o,'x',{value:1});
//{value: 1, writable: false, enumerable: false, configurable: false}   
Object.getOwnPropertyDescriptor(o, 'x');
复制代码
  • 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。
  • 如果属性是不可配置的,则不能修改它的可配置性和可枚举性。
  • 如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转换为数据属性。
  • 如果数据属性是不可配置的,则不能将它转换为存取器属性。
  • 如果数据属性是不可配置的,则不能将它的可写性从false修改为true,但可以从true修改为false。
  • 如果数据属性是不可配置且不可写的,则不能修改它的值。然而可配置但不可写属性的值是可以修改的(实际上是先将它标记为可写的,然后修改它的值,最后转换为不可写的)。

==以两条下划线作前缀,两条下划线作后缀,以表明它们是非标准的方法==

对象的属性

原型属性 prototype attribute 对象的原型属性是用来继承属性的。通常"o的原型属性" 叫 "o的原型"。 Object.getPrototypeOf() 查询对象的原型 (o.constructor.prototype方法不可靠)
isPrototypeOf() 检测一个对象是否是另一个对象的原型(或处于原型链中)

类属性 class attribute 表示对象的类型信息。

function classof(o) {
    if (o === null) return "Null";
    if (o === undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8, -1);
}

classof(null)//=>"Null"
classof(1)//=>"Number"
classof("")//=>"String"
classof(false)//=>"Boolean"
classof({})//=>"Object"
classof([])//=>"Array"
classof(/./)  //=>"RegExp"
classof(new Date())  //=>"Date"
classof(window)  //=>"Window"
function f(){}   
classof(new f())  //=>"Object"
复制代码

可扩展性属性 extensible attribute 表示是否可以给对象添加新属性。所有内置、自定义对象都是显式可扩展。 目的是将对象“锁定”,以避免外界的干扰。

  • Object.jsExtensible()来判断对象是否是可扩展的。
  • Object.preventExtensions()将对象转换为不可扩展的。一旦将对象转换为不可扩展的话就无法转换为可扩展的了。只影响对象本身的可扩展性,该对象的原型添加属性,该对象会继承新属性。
  • Object.seal() 能够将对象设置为不可扩展的,将对象的所有自有属性都设置为不可配置的。
  • 不能给这个对象添加新属性,而且它已有的属性也不能删除或配置,不过它已有的可写属性依然可以设置。对于那些已经封闭(sealed)起来的对象是不能解封的。Object.isSealed()来检测对象是否封闭。
  • Object.freeze() “冻结”(frozen)。 将对象设置为不可扩展的和将其属性设置为不可配置的之外,还可以将它自有的所有数据属性设置为只读(如果对象的存取器属性具有setter方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)。 Object.isFrozen()来检测对象是否冻结。

序列化对象(serialization)

指将对象的状态转换为字符串,也可将字符串还原为对象.

  • NaN、Infinity和-Infinity序列化的结果是null
  • 日期对象序列化的结果是ISO格式的日期字符串(Date.toJSON()函数),但JSON.parse()依然保留它们的字符串形态,而不会将它们还原为原始日期对象。
  • 函数、RegExp、Error对象和undefined值不能序列化和还原。
  • JSON.stringify()只能序列化对象可枚举的自有属性。

猜你喜欢

转载自juejin.im/post/7031825670590169095