理解对象

  • 每个对象都是基于一个引用类型创建的
  • JS 中的所有对象都是继承自 Object 对象

对象特性

  • 结构类似「字典」 :对象的属性类似键 / 值对;属性的名称为字符串,属性的值为任意类型。
  • 原型继承:Js 的对象可继承原型的属性。
  • 动态结构:可动态新增、删除对象的属性。
  • 引用类型:js 中的对象为引用类型。a 为一个对象,b=a,修改 b 也会造成 a 的修改。

创建对象的方式

浅谈 JS 创建对象的 8 种模式 - 全栈白菜烹饪指南 - SegmentFault 思否

有 3 种定方形式:对象字面量、new 构造函数和 Object.create()

对象字面量成为创建对象的首选模式

每一种创建方式继承的原型对象都不同

Object.create() 允许为创建的对象选择原型对象,不需要定义一个构造函数

  • 对象字面量:Object.prototype

    
      var obj = {name:'xusir'}
      console.log(obj.constructor.prototype); // Object() 对象字面量的原型为Object
      console.log(obj.constructor.prototype === Object.prototype); // true
    
  • new 构造函数:构造函数的 prototype 属性

      // 创建构造函数
      function People(name) {
          this.name;
      }
    
      // 创建对象
      var p = new People('Tom');
      console.log(p.constructor.prototype); // People{} 原型为构造函数的 prototype
      console.log(p.constructor.prototype === People.prototype); // true
    
  • Object.create(prototype, propertyDescriptor):默认原型为 参数 prototype;若 prototype 为null,对象的原型为 undefined

      // 1.建立一个原型为null的对象
      var o = Object.create(null, {
          name: {
              value: 'tom'
          }
      });
      console.log(o.constructor); // undefined 
    
      // 2.创建一个原型为Array的对象
      var array = Object.create(Array.prototype, {});
      console.log(array.constructor); // function Array 构造函数
      console.log(array.constructor.prototype); // []  原型对象为数组
    

对象实例的属性和方法

当我们用 obj.xxx 访问一个对象的属性时,JavaScript 引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到 Object.prototype 对象,最后,如果还没有找到,就只能返回 undefined。

var arr = [1, 2, 3];

//其原型链是
arr 》 Array.prototype 》 Object.prototype  》 null

⚠️ 如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长

对象属性是无序的

一个对象是一个 Object 类型的实例。它是由一些未排序的元素组成的集合,其中包含了原始变量,对象,和函数。一个对象的属性所对应的函数被称为方法

对象的输出是无序的,但是数组却是有序的,所以为了保证顺序,搞成数组再输出

怎么解决呢❓

Map()的营救;使对象属性有顺序

1. constructor 属性 constructor 属性是保存当前对象的构造函数

var obj1 = new Object();
obj1.id = "obj1";
var obj2 = {
    "id": "obj2"
};

console.log(obj1.constructor);//function Object(){}
console.log(obj2.constructor);//function Object(){}

2. hasOwnProperty(propertyName) 方法 判断该属性是否在当前对象实例中,而不是在对象的原型链中

var arr = [];        
console.log(arr.hasOwnProperty("length"));//true
console.log(arr.hasOwnProperty("hasOwnProperty"));//false

3. isPrototypeOf(Object) 方法 isPrototype 方法接收一个对象,用来判断当前对象是否在传入的参数对象的原型链上

function MyObject() {}
var obj = new MyObject();
console.log(Object.prototype.isPrototypeOf(obj)); // true

MyObject 是继承自 Object 对象的,而在 JS 中,继承是通过 prototype 来实现的,所以 Object 的 prototype 必定在 MyObject 对象实例的原型链上

4. propertyIsEnumerable(prototypeName) 方法 判断给定的属性是否可以被 for..in 语句给枚举出来

console.log(obj.propertyIsEnumerable("constructor")); // false

5. toString() 方法 返回对象的字符串表示。

var obj = {};
console.log(obj.toString());//[object Object]

6. valueOf() 方法 返回对象的原始值,可能是字符串、数值或 bool 值等,看具体的对象。

var obj = {
    name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}

var arr = [1];
console.log(arr.valueOf());//[1]

var date = new Date();
console.log(date.valueOf());//1456638436303

属性类型

ECMAScript中有 2 种属性:数据属性访问器属性

1. 数据属性

数据属性我们可以理解为我们平时定义对象时赋予的属性,它可以进行读和写。

但 ES5 中定义了一些特性,这些特性是用来描述属性的各种特征,特性是内部值,不能直接访问到。特性通过用两对方括号表示,比如[[Enumerable]]。属性的特性会有一些默认值,要修改特性的默认值,必须使用 ES5 定义的新方法 Object.defineProperty 方法来修改。

4 个描述其行为的特性

  • [[Configurable]]:能否修改属性(默认值 true)
  • [[Enumerable]]:能否能被 for...in 循环或 Object.keys 方法遍历得到(默认值 true)
  • [[Writeable]]:能否修改属性的值(默认值 true)
  • [[Value]]:包含这个属性的数据值(默认值 undefined)

直接在对象上定义的属性,它们的 [[Configurable]]、[[Enumerable]] 和 [[Writable]]特性都被设置为 true,而 [[Value]] 特性被设置为指定的值。例如:

var obj = {
    name:"bob"
}

Q:如何修改属性默认的特性呢❓

  • 必须使用 ES5 的 Object.defineProperty() 方法
  • 调用 Object.defineProperty() 方法时,如果不指定,configurable、enumerable 和 writable特性的默认值都是false

     var obj = {}
    
     Object.defineProperty(obj, 'name', {value:'初始值'});
    
     obj.name = 'bob';
     console.log(obj.name); // 初始值
    
     // 等价于下面 //////////////// 
    
     Object.defineProperty(obj, 'name', {
         value: '初始值',
         enumerable: false,
         configurable: false,
         writable: false,
     });
    

Q:configurable 与 writable 的区别❓

  • writable:false

    该属性值不能被修改

  • configurable:false

    该属性不能被删除,以及除 writable 特性外的其他特性是否可以被修改。

  • writable:false && configurable:false

    该属性不能被删除,以及所有特性是否可以被修改

2. 访问器属性

访问器属性有点类似于 C# 中的属性,和数据属性的区别在于,它没有数据属性的 [[Writable]] 和 [[Value]] 两个特性,而是拥有一对 getter 和 setter 函数

[[Get]]:读取属性时调用的函数,默认是undefined [[Set]]:设置属性时调用的函数,默认是undefined

getter 和 setter 是一个很有用的东西,假设有两个属性,其中第二个属性值会随着第一个属性值的变化而变化

var person = {
    age: 10
}

Object.defineProperty(person, "type", {
    get: function () {
        if (person.age > 17) {
            return "成人";
        }
        return "小孩";
    }
})

console.log(person.type);//小孩

person.age = 18;
console.log(person.type);//成人

通过修改 age 的值,type 的值也会相应的修改,这样我们就不用再手动的去修改 type 的值了

下面这种方式也是可以实现同样的效果:

var person = {
    _age: 10,
    type: "小孩"
} 

Object.defineProperty(person, "age", {
    get: function () {
        return this._age;
    },
    set: function (newValue) {
        this._age = newValue;
        this.type = newValue > 17 ? "成人" : "小孩";
    }
})
console.log(person.type);

person.age = 18;
console.log(person.type);

⚠️ 关于访问器属性,有几点要注意:

  • 严格模式下,必须同时设置 get 和 set
  • 非严格模式下,可以只设置其中一个,如果只设置 get,则属性是只读的,如果只设置 set,属性则无法读取
  • Object.defineProperty 是 ES5 中的新方法,IE9(IE8 部分实现,只有 dom 对象才支持)以下浏览器不支持,一些旧的浏览器可以通过非标准方法 defineGetter() 和 defineSetter() 来设置,这里就不说明了,有兴趣的同学可以查找相关资料。

3. 特性操作的相关方法 ES5 提供了一些读取或操作属性特性的方法,前面用到的 Object.defineProperty 就是其中之一。以下是一些比较常用的方法:

3.1 Object.defineProperty 定义一个对象的属性,这个方法前面我们已经用到多次

3.2 Object.defineProperties 和 defineProperty 类似,不同的是它可以用来同时定义多个属性

var obj = {};
Object.defineProperty(obj, {
    "name": {
        value: "name",
        configurable: true,
        writable: true,
        enumerable: true
    },
    "age": {
        value: 20 
    }
});

3.3 Object.getOwnPropertyDescriptor 一个读取特性值的方法,该方法接收对象及其属性名作为两个参数,返回一个对象,根据属性类型的不同,返回对象会包含不同的值。

var person = {
    _age: 10,
    type: "小孩"
}
Object.defineProperty(person, "age", {
    get: function () {
        return this._age;
    },
    set: function (newValue) {
        this._age = newValue;
        this.type = newValue > 17 ? "成人" : "小孩";
    }
})

console.log(Object.getOwnPropertyDescriptor(person, "type"));//Object {value: "成人", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, "age")); //Object {enumerable: false, configurable: false, get: function(),set: function ()}

序列化

通过调用 JSON 方法,可以将对象序列化成字符串 / 将 Json 字符串反序列化成对象

1. JSON.stringify(object):把对象转换成一个字符串

var o = {
    x: 1,
    y: 2
}

JSON.stringify(o); // {"x":1,"y":2} 返回一个字符串

函数也是一个对象,它的原型链是

function foo() {
    return 0;
}

foo 》 Function.prototype  》 Object.prototype 》 null

2. JSON.parse(jsonStr) :将一个 Json 字符串转换为对象

var str = '{ "x":1,"y":2 }'; // 字符串的属性名要用引号框起来
var o = JSON.parse(str);
console.log(o.x); //  1 输出对象属性 x 的值

存在性

myObject.a 的属性访问返回值可能是 undefined,但是这个值有可能是属性中存储的 undefined,也可能是因为属性不存在所以返回 undefined。那么如何区分这 2 种情况呢?

我们可以在不访问属性值的情况下判断对象中是否存在这个属性:

var myObject = { 
    a:2
};

("a" in myObject); // true
("b" in myObject); // false

myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false

⚠️ 看起来 in 操作符可以检查容器内是否有某个值,但是它实际上检查的是某个属性名是否存在。对于数组来说这个区别非常重要,4 in [2, 4, 6] 的结果并不是你期待的 True,因为 [2, 4, 6] 这个数组中包含的属性名是 0、1、2,没有 4。

利用 Object.freeze() 提升性能

Object.freeze() 是 ES5 新增的特性,可以冻结一个对象,防止对象被修改。

如果你有一个巨大的数组或 Object,并且确信数据不会修改,使用 Object.freeze() 可以让性能大幅提升。在我的实际开发中,这种提升大约有 5~10 倍,倍数随着数据量递增。

vue 1.0.18+ 对其提供了支持,对于 data 或vuex 里使用 freeze 冻结了的对象,vue 不会做 getter 和 setter 的转换

扩展、密封及冻结 3 大特性

浅谈 JS 对象之扩展、密封及冻结三大特性 - 全栈白菜烹饪指南 - SegmentFault 思否

1. 扩展特性

如果一个对象可以添加新的属性,则这个对象是可扩展的。 让这个对象变的不可扩展,也就是不能再有新的属性

  • Object.isExtensible(obj)

    判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)

  • Object.preventExtensions(obj)

    让一个对象变的不可扩展(永远不能再添加新的属性)

2. 密封特性

密封对象是指那些不可扩展 的,且所有自身属性都不可配置的(non-configurable)对象。

或则说 密封对象是指那些不能添加新的属性,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但可能可以修改已有属性的值的对象

  • Object.isSealed(obj)

    判断一个对象是否是密封的(sealed)

  • Object.seal(obj)

    让一个对象密封,并返回被密封后的对象

3. 冻结特性

一个对象是冻结的(frozen)是指它不可扩展,所有属性都是不可配置的(non-configurable),且所有数据属性(data properties)都是不可写的(non-writable)。

或则说 冻结对象是指那些不能添加新的属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象。也就是说,这个对象永远是不可变的。

  • Object.isFrozen(obj)

    判断一个对象是否被冻结(frozen)

  • Object.freeze(obj)

    冻结一个对象

源码学习

Q & A

Q:宿主对象和原生对象的区别❓

原生对象 ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript实现提供的对象”。 “本地对象”包含哪些内容:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError。 由此可以看出,简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。

内置对象 ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript实现提供的、独立于宿主环境的所有对象,在 ECMAScript程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。 同样是“独立于宿主环境”。根据定义我们似乎很难分清“内置对象”与“本地对象”的区别。而ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。如此就可以理解了。内置对象是本地对象的一种。

宿主对象 何为“宿主对象”?主要在这个“宿主”的概念上,ECMAScript中的“宿主”当然就是我们网页的运行环境,即“操作系统”和“浏览器”。 所有非本地对象都是宿主对象(host object),即由 ECMAScript实现的宿主环境提供的对象。所有的BOM和DOM都是宿主对象。因为其对于不同的“宿主”环境所展示的内容不同。其实说白了就是,ECMAScript官方未定义的对象都属于宿主对象,因为其未定义的对象大多数是自己通过ECMAScript程序创建的对象。

Q:为什么 typeof(null) 是 "object"?

从逻辑角度来看,null值表示一个空对象指针,而这正是使用typeof操作符检测null值时会返回“object”的原因 --《JavaScript高级程序设计 第3版 》P100

  1. null 不是一个空引用, 而是一个原始值, 参考ECMAScript5.1中文版 4.3.11节; 它只是期望此处将引用一个对象, 注意是"期望", 参考 null - JavaScript.

  2. typeof null结果是object, 这是个历史遗留bug, 参考 typeof - JavaScript

  3. 在ECMA6中, 曾经有提案为历史平凡, 将type null的值纠正为null, 但最后提案被拒了. 理由是历史遗留代码太多, 不想得罪人, 不如继续将错就错当和事老, 参考 harmony:typeof_null [ES Wiki]

🌱 学习参考