JavaScript-读 You Dont Know JS, Object到底是什么

来源:互联网 发布:淘宝客网站模板 编辑:程序博客网 时间:2024/05/21 19:25

这篇博客是读You Dont Know JS系列书中this & Object Prototypes这本书后总结的第二篇博客(第一篇讲this到底是什么)。
这篇博客讲对象,其中会涉及到一些让我们困惑已久的问题,比如:对象的数据属性和访问器属性,对象属性(property)的特性(attribute)。

数据类型

关于对象你首先需要知道,JavaScript中“一切都是对象”这句话是错的。JavaScript中数据类型有六种:

  • string
  • number
  • boolean
  • null
  • undefined
  • object

JavaScript中存在几种内建对象:
* String
* Number
* Boolean
* Object
* Function
* Array
* Date
* RegExp
* Error
我们之所以可以调用'abc'.charAt(1)是因为string类型在这种情况下会自动转换成String。这是一个很基础的点,不过多展开。

创建对象

创建对象两种方式:

  • 对象字面量
  • 构造函数
// 对象字面量var obj = {    key: value    };// 构造函数var obj = new Object();obj.key = value;

对象属性(property)的特性(attribute)

对象的属性看起来像是在对象上,但其实引擎会根据自己的实现来存储这些值,而且通常不是放在对象内部,对象内存储的是这些属性的名称,它们像指针一样指向存储的地方,技术上讲,叫引用(reference)。

什么是特性

对象的属性分为两类:数据属性和访问器属性。数据属性就是存有一个值或者一个引用的属性,访问器属性是,在读取该属性时会调用其内部的getter函数,这个函数负责返回有效值,在写入该属性值会调用其内部的setter函数,这个函数负责决定如何写入新值。

不管是数据属性还是访问器属性,都有一些描述这些属性的“文档”,这些“文档”以对象的形式存在,且“文档”的内容固定,被称为特性。

数据属性有四个描述其行为的特性:

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为true。
  • [[Enumerable]]:表示能否通过for…in…循环遍历到该属性,默认值为true。
  • [[Writable]]:表示能否修改属性的值,默认为true。
  • [[Value]]:该属性的数值。

访问器属性也有四个表述其行为的特性:

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。默认值为true。
  • [[Enumerable]]:表示能否通过for…in…循环遍历到该属性,默认值为true。
  • [[Get]]:在读取属性时调用的函数。默认值为undefined。
  • [[Set]]:在写入属性时调用的函数。默认值为undefined。

如何使用特性

获得一个属性的特性:

let cat = {    name: 'miao',    age: 1}let nameDescriptor = Object.getOwnPropertyDescriptor(cat, 'name');console.log(nameDescriptor);

这里写图片描述

定义数据属性的特性

Object.defineProperty(cat, 'age', {    value: 2,    writable: false,    configurable: true,    enumerrable: true});console.log(cat.age);cat.age = 5;console.log(cat.age);

设置age属性不可写以后,再试图给age赋值,严格模式下会报错,非严格模式下会静默赋值失败。下图是严格模式下执行结果。
这里写图片描述

定义访问器属性的特性

let cat = {    name: 'miao',    _age: 1};Object.defineProperty(cat, 'age', {    get: function(){        return this._age;    },    set: function(value){        if(value > 3){            // cat的年龄不能大于3            this._age = value % 3;        }else if(value < 0){            console.log('Wrong value');        }else{            this._age = value;        }    }});console.log(cat.age);cat.age = 8;console.log(cat.age);

这里写图片描述

特性[[Enumerable]]对遍历的影响

注意,[[Enumerable]]不仅影响到for…in…,数组的forEach、ES6的新遍历器for…of…都会因为该特性为false而不能遍历到某属性。for …in…遍历的是对象的属性名,我们如果想直接遍历对象的属性值可以使用for…of…。
Array, Map, Set等数据类型内建iterator遍历器,因而可以使用for…of…遍历,Object却不行。但是我们可以自己定义一个iterator。

var myObject = {    a: 123,    b: 321};// 定义iterator,并让其不可被遍历器遍历到——enumerable: falseObject.defineProperty( myObject, Symbol.iterator, {    enumerable: false,    writable: false,    configurable: true,    value: function() {        var o = this;        var idx = 0;        var ks = Object.keys( o );        return {            next: function() {                return {                    value: o[ks[idx++]],                    done: (idx > ks.length)                };            }        };    }} );var it = myObject[Symbol.iterator]();it.next(); // { value:2, done:false }it.next(); // { value:3, done:false }it.next(); // { value:undefined, done:true }// iterate `myObject` with `for..of`for (var v of myObject) {    console.log( v );    // 123    // 321}

至此,我有个问题,既然for…of…这么好用,为什么Object不内建iterator呢? 没有找到ECMAScript的解释,StackOverflow这样说。

拷贝

这部分很多人都懂,简单说。对象的属性可能也是一个object(包括几种内建对象,见上文),这个时候这个属性保存的实际是object的一个引用,当我们拷贝对象的时候就要考虑:我们是要拷贝这个引用还是把引用的object也拷贝过来,前者叫做浅拷贝(shallow copy)后者叫深拷贝(deep copy)。

深拷贝当然会减少外部对象对我们自己写的对象的影响:

let objA = {    name: 'A'};let objB = {    name: 'B',    friend: objA};

当我们希望拷贝一个objB的副本objBCopy时候,如果使用深拷贝可以避免objA改变对objBCopy的影响,可是当出现一个循环引用的时候深拷贝会走近一个死胡同。如何以及何时打破这种循环目前没有统一准,Object.assign()方法是浅拷贝,在使用这个方法时候请注意外部对象可能对拷贝结果的影响。

不可变对象

这也是个老话题,理论上讲让一个对象不可以被其他开发者随意更改有利于程序的健壮性,但是由于模块和不规范开发,我们其实不太常使用不可变对象。

不可变对象有几个级别,越来越严格:

  • 对象常量,[[Writable]]和[[Configurable]]都为false,不能被更改和删除
  • 不可扩展对象,不能给对象添加新的属性,用Object.preventExtens方法完成
  • 密封对象,密封对象不可扩展且已有属性的[[Configurable]]特性被设置为false,不能删除属性,用Object.seal方法完成
  • 冻结对象,密封且[[Writable]]为false,用Object.freeze方法完成

可以查看《JavaScript高级程序设计》22.2,学习具体内容,不再赘述。

0 0