整顿Object的一些要领

ES5 和 ES6 中 Object 增添了许多新的要领,如今许多开源代码中有用到了这些,本日来整顿一番。

Object.assign()

这是ES6新增添的要领,Object.assign()用来复制源对象的一切可罗列属性复制到目的对象中,要领返回目的对象。语法以下:

Object.assign(target, ...source);

source对象可以有许多个,比方:

let target = {name: 'target'};
let source1 = {age: 23};
let source2 = {email: 'zhanglun1410@gmail.com'};
// ...
// let sourceN ={.....};
traget = Object.assign(target, source1, source2);

如果源对象和目的对象的属性的key雷同,目的对象的属性将会被源对象中的属性掩盖。关于多个源对象来讲,如果有雷同的key,右侧的属性将掩盖左侧的属性。这个要领只能将源对象的可罗列对象和本身的属性复制给目的对象。

什么是可罗列对象()?
可罗列属性是指那些内部 “可罗列” 标志设置为true的属性,关于经由过程直接的赋值和属性初始化的属性,该标识值默以为即为true,关于经由过程Object.defineProperty等定义的属性,该标识值默以为false。可罗列的属性可以经由过程 for…in 轮回举行遍历(除非该属性名是一个 Symbol)。

关于源对象,Object.assign运用[[Get]],而在目的对象上运用[[Set]],也就是说,运用这个要领会源对象的getter和目的对象的setters。所以其本质就是定义或许复制一个新的属性。如果守候兼并的源对象包含了getters,那就不太合适用来将源对象兼并到原型中。如果复制的属性到原型里,包含它们的可罗列属性,那末应当运用 Object.getOwnPropertyDescriptor() 和 Object.defineProperty() 。String 和 Symbol 属性都是会被复制的。

如果遇到了一个毛病,比方目的对象的某个属性是不可修正的,会抛出一个TypeError的毛病吗,目的对象坚持稳定

var foo = {}
Object.defineProperty(foo, 'name', {
  writable: false,
  value: 'zhanglun'
});
Object.assign(foo, {name: 'zhangxiaolun'}); // TypeError: Cannot assign to read only property '1' of object '#<Object>'

如果源对象是null或许undefined,Object.assign()不会抛出毛病:

var foo = {name: 'zhanglun'};
Object.assign(foo, null, undefined);
console.log(foo); // foo: {name: 'zhanglun'}

Object.create()

经由过程指定的原型对象和属性,建立一个新的对象。语法以下:

Object.create(proto, [,. propertiesObject]);

第一个参数是一个对象,可以是一个平常的对象,比方:{name: 'zhanglun'},也可以是一个新建立的对象的原型(prototype),比方:new Array().prototype。无论是那种,都是 JavaScript 中的 Object,其属性都被增添到返回的对象原型中;第二个参数是可选的,然则不能是undefined,该对象本身具有的可罗列属性会被增添到新建立的对象上,其原型链上的属性是无效的。如果第一个参数不是null或许一个对象值,将会抛出TypeError非常。

Object.create()最直接的作用是基于一个对象建立新的对象,更多时刻用在了原型链继续上,先来看看 JavaScript
中建立对象的几种要领:

  • 对象字面量

var o = {a: 1};

// o这个对象继续了Object.prototype上面的一切属性
// 所以可以如许运用 o.hasOwnProperty('a').
// hasOwnProperty 是Object.prototype的本身属性。
// Object.prototype的原型为null。
// 原型链以下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 数组都继续于Array.prototype 
// (indexOf, forEach等要领都是从它继续而来).
// 原型链以下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函数都继续于Function.prototype
// (call, bind等要领都是从它继续而来):
// f ---> Function.prototype ---> Object.prototype ---> null
  • 组织函数

在 JavaScript 中,组织器实在就是一个平常的函数。当运用 new 操纵符 来作用这个函数时,它就可以被称为组织要领(组织函数)。如果没有 new 关键字而是直接挪用的话,相称因而在当前作用域上挪用,此时函数中如果有 this 的话,this 指向的是当前作用域。

function Graph() {
  this.vertexes = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};

var g = new Graph();
// g是天生的对象,他的本身属性有'vertices'和'edges'.
// 在g被实例化时,g.[[Prototype]]指向了Graph.prototype.
Graph();
console.log(window.vertexes); // 在全局作用域中挪用,意外埠增添了全局变量
  • 运用 Object.create()

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继续而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 由于d没有继续Object.prototype
  • ES6 中的 Class 关键字

ES6 引入了一套新的关键字用来完成 class。这是一个语法糖,其本质照样基于原型的。这些新的关键字包含 class, constructor, static, extends, 和 super。关于 Class的运用,转头再开一篇文章深切进修。

"use strict";

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

运用 Object.create() 完成继续

下面是一个运用 Object.create()完成类的继续的例子。

function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类的原型要领
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// 子类
function Rectangle() {
  Shape.call(this); // 挪用组织函数
}

// 子类继续父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle);// true
console.log('Is rect an instance of Shape?', rect instanceof Shape);// true
rect.move(1, 1); // Outputs, 'Shape moved.'

Object.create VS new

前面说到的,Object.create实际上是将第一个参数(无论是原型对象照样平常对象)的属性增添到新建立对象的原型中,这也就意味着,经由过程new Function()中定义的属性和要领是没法经由过程 create()要领增添到新建立对象中的。

Object.create建立一个新的对象,这个对象“继续”了第一个参数。组织函数新建立的对象“继续”组织函数的prototype。

let o = new SomeConstructor();  // o 直接继续自`SomeConstructor.prototype`。

二者的最显著的不同之处在于:如果 Object.create()的第一个参数是null,新建立的对象不会“继续”自任何地方,没有原型,也没有往上追溯的原型;在运用组织函数时,如果将其原型设置为 null,SomeConstructor.prototype = null;,新建立的对象将会“继续”自 Object 的 prototype。

Object.freeze()

字面意义就是将一个 object“冻住”:不能增添新的属性;不能删除现有的属性;不能修正现有属性,包含属性的enumerability, configurability和 writability。这个要领返回一个不可修正的对象,运用语法:

Object.freeze(obj)

任何尝试修正该对象的操纵都邑失利,多是寂静失利,也可能会抛出非常。在严厉形式中会抛出非常(当地测试下来严厉形式也不会报错)。数据属性的值不可变动,接见器属性(有getter和setter)也一样,但由因而函数挪用,给人一种修正了这个属性的错觉。如果一个属性的值是个对象,则这个对象中的属性是可以修正的,除非它也是个凝结对象。

let foo = {
  name: 'zhanglun',
  age: 23,
}

console.log(foo.name); // 'zhanglun'
console.log(foo.age); // 23

foo.name = 'zhanglun1410';
foo.age = 24;

console.log(foo.name); // 'zhanglun1410'
console.log(foo.age); // 24

Object.freeze(foo);

foo.name = 'zzzz'; // 寂静失利
console.log(foo.name); // 'zhanglun1410'

'use strict';
foo.name = 'zzzz'; // TypeError 或许 寂静失利
console.log(foo.name); // 'zhanglun1410'

Object.freeze VS const

被凝结的对象是不可转变的。然则它不一定是常量。对常量而言,它的一切援用,无论是直接的照样间接的都是援用的不可转变的对象。string,number,和 boolean 老是不可变的(当你把一个变量从字符串 A 修正到字符串 B时,A 和 B 都是不可变的,A 照样 A,B 也照样 B,只不过变量的之前指向的是 A,修正以后指向了 B)。平常来讲,平常不会建立一个对象常量,也不存在freezeAll()如许的要领。

const 用于声明常量,将变量绑定到一个不可修正的对象,常量终究指向的是一个不可修正的对象,比方一个被凝结的对象,而 Object.freeze 作用在对象的值上,将一个对象变成不可修正的对象。

深度凝结对象

前面提到的,Object.freeze作用在对象的属性上,使对象的属性不可修正。而如果属性值也是一个对象的话,依旧可以修正,除非这个对象也被凝结了。因而,可以把 Object.freeze 明白成是“浅凝结”。可以编写分外的代码来完成“深凝结”:

obj1 = {
  internal: {}
};

Object.freeze(obj1);
obj1.internal.a = 'aValue';

obj1.internal.a // 'aValue'

// 深度凝结
function deepFreeze(obj) {

  // 获取到对象的属性的名字
  var propNames = Object.getOwnPropertyNames(obj);

  // 先凝结内部的对象
  propNames.forEach(function(name) {
    var prop = obj[name];

    // Freeze prop if it is an object
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // 凝结 obj
  return Object.freeze(obj);
}

obj2 = {
  internal: {}
};

deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined

Object.freeze 的注意事项

在 ES5 中,如果参数不是一个对象,是一个原始数据类型,会抛出 TypeError。在 ES6 中,不是对象的参数的会被当作是一个已被凝结的平常对象,只是返回这个参数。

Object.defineProperty()

Object.defineProperty是ES5新增的一个要领,可以给对象的属性增添更多的掌握。语法以下:

Object.defineProperty(obj, prop, descriptor)

前面两个参数很简单,修正的对象和修正或许新增的属性,偏重引见一下第三个参数:属性形貌符。

ES5 中定义了一个名叫“属性形貌符”的对象,用于形貌了的种种特征,它本身是一个 Object。属性形貌符对象有4个属性:

  • configurable:可设置性,掌握着其形貌的属性的修正,示意可否修正属性的特征,可否把属性修正为接见器属性,或许可否经由过程delete删除属性从而从新定义属性。默认值为true。

  • enumerable:可罗列性,示意可否经由过程for-in遍历获得属性。默认值为true。

  • writable:可写性,示意可否修正属性的值。默认值为true。

  • value:数据属性,示意属性的值。默认值为undefined。

和两个存取器属性,分别是get和set,可以替代value和writable。

  • get:在读取属性时挪用的函数。只指定get则示意属性为只读属性。默认值为undefined。

  • set:在写入属性时挪用的函数。只指定set则示意属性为只写属性。默认值为undefined。

属性形貌符只能在Object.definePropertyObject.defineProperties中运用。

var o = {}; // Creates a new object

// Example of an object property added with defineProperty with a data property descriptor
// 增添属性 a,值为37,并设置属性形貌符
Object.defineProperty(o, 'a', {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true
});


var bValue = 38;
Object.defineProperty(o, 'b', {
  get: function() { return bValue; },
  set: function(newValue) { bValue = newValue; },
  enumerable: true,
  configurable: true
});
o.b; // 38
// o 对象中存在属性 b,他的值为38;
// 只需 o.b没有从新定义,它的值永远都是38

// 接见器不能和 value或许 writable混在一升引
Object.defineProperty(o, 'conflict', {
  value: 0x9f91102,
  get: function() { return 0xdeadbeef; }
});
// 抛出一个毛病 Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>

Object.defineProperties()

比拟 Object.defineProperty, Object.defineProperties可以说是前者的升级版,可以一次同时定义多个属性,语法略有不同:

let obj = {};
Object.defineProperties(obj, {
  "property1": {
    value: true,
    writable: true
  },
  "property2": {
    value: "Hello",
    writable: false
  },
  "property3": {
    get: function() {
      return 'Hello, Object.defineProperties';
    },
    set:function() {
      this.property2 = 'xxxxxx';
    }
  }
  // etc. etc.
});

参考资料:

  1. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

  3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

  4. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

  5. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

  6. http://stackoverflow.com/a/17952160

    原文作者:张伦
    原文地址: https://segmentfault.com/a/1190000008216793
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞