【JavaScript必知】深度挖掘 Object 對象的使用

介紹

在javascript中, 數據類型主要分爲原始類型和引用類型兩種。而一切引用類型都來自於Object的拷貝。全部引用類型的原型鏈均可以追溯到 Objectjavascript

Object 構造函數屬性

JavaScript 內置的一些構造函數有 Object, Function, Number, String, Boolean, Array, RegExp 等等, 它們主要有兩個共有的屬性。java

  • length 構造函數參數個數
  • prototype 構造函數原型對象

Object原型鏈

  • Object.getPrototypeOf
  • Object.isPrototypeOf
  • Object.hasOwnProperty

一切引用對象的原型都來自 Object.prototype

測試各個數據類型的引用狀況數組

var a = {},
    b = [],
    c = function () {},
    d = Function,
    e = String;
[a, b, c, d, e].forEach(function (val) {
    // all return true
    console.log(val instanceof Object);
});

每個引用類型對象, 都含有一個原型屬性__proto__, 它負責控制對象的原型屬性和方法的查詢使用函數

建立無Object.prototype的原型鏈對象

// method 1
var obj1 = Object.create(null);

// method 2
var obj2 = {};
Object.setPrototypeOf(obj2, null);

// method 3
var obj3 = {};
obj3.__proto__ = null;

[obj1, obj2, obj3].forEach(function (item) {
  console.log(item instanceof Object); // false
});

__proto__prototype

__proto__ 隱式原型, prototype 顯示原型.
實例對象經過隱式原型__proto__ 匹配找到對應的函數和屬性. 而prototype是每個構造函數都存在的一個屬性。其中prototype 包含 constructor屬性oop

var o = {};
'__proto__' in o; // return true, 說明 字面量對象存在一個隱式屬性

// 指定隱式屬性
function Foo () {}
o.__proto__ = Foo.prototype;

o instanceof Foo; // return true
o instanceof Object; // return true

設置隱藏原型屬性

var o = {};
'__proto__' in o;

function Foo () {}
function Bar () {}
Bar.prototype = new Foo();
Bar.prototype.constructor = Bar;

// 方法一, 直接設置 __proto__值
o.__proto__ = Foo.prototype;
console.log(o instanceof Foo); // return true;

// 方法二, 使用 setPrototypeOf
Object.setPrototypeOf(o, Bar.prototype); // 設置新原型
console.log(o instanceof Bar); // return true;

// 獲取對象隱式屬性
Object.getPrototypeOf(o) === Bar.prototype; // return true

// 檢查原型 是否存在於對象屬性的鏈中
Bar.prototype.isPrototypeOf(o); // true
Foo.prototype.isPrototypeOf(o); // true
Object.prototype.isPrototypeOf(o); // true

獲取對象的實例屬性

  • Object.keys
  • Object.getOwnPropertyNames
// return []
Object.keys(Object.prototype)

// return ["constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "toLocaleString"]
Object.getOwnPropertyNames(Object.prototype)

Object.keys 返回一個對象的實例可枚舉屬性, 若是使用了Object.defineProperty改變了對象的某個屬性, 則沒法經過Object.keys返回屬性進行遍歷屬性, 也沒法使用 for-in循環。學習

var obj = {
    foo: function () {}
};

// return ['foo']
Object.keys(obj);

Object.defineProperty(obj, 'foo', {
   enumerable: false
});

// return []
Object.keys(obj);

// empty loop
for (var name in obj) {
  console.log(name);
}

// return false
obj.hasOwnProperty('foo');

Object.getOwnPropertyNames() 返回自身實例屬性名稱, 無視enumerable: false測試

var obj = {
    foo: function () {},
    bar: 'hello world'
};

Object.defineProperty(obj, 'foo', {
    // foo 已被定義, 因此須要顯示設置 false
    enumerable: false
});

Object.defineProperty(obj, 'foo2', {
    value: function () {},
    // foo2 未被定義, 默認enumerable爲false
    enumerable: true
});

// 'bar', 'foo2'
Object.keys(obj);

// 'foo', 'bar', 'foo2'
Object.getOwnPropertyNames(obj);

'foo' in obj // return true

in 操做符, 檢查屬性是否在可以獲取, 無視屬性this

對象定義屬性

  • Object.defineProperty
  • Object.getOwnPropertyDescriptor

屬性描述通常是由enumrable, value, get, set, configurable, writeable, value 組成, 其中 get, setvalue 爲互斥關係prototype

定義一個對象的描述

var obj = Object.create(null);

Object.defineProperty(obj, 'foo', {
    value: 'foo',
    configurable: false,
    writeable: false,
    enumerable: false
});

obj.foo = 'change foo';
console.log(obj.foo); // still foo

Object.defineProperty(obj, 'foo', {
    writeable: true,
    configurable: true
});

obj.foo = 'change foo 2';
console.log(obj.foo); // still foo
'foo' in obj; // return true
  • 一旦被定義了configureable: false, 那麼後續再次 defineProperty時不會生效。
  • writeable: false 的狀況下, 沒法修改屬性的 value

屬性描述中的 valueget, set

var obj = Object.create(null);

// throw Error 
// Uncaught TypeError: Invalid property descriptor. 
// Cannot both specify accessors and a value or writable attribute
Object.defineProperty(obj, 'foo', {
    value: 'foo',
    set: function (val) {
        // this 指向 obj
        this._foo = val;
    },
    get: function () {
        return this._foo;
    }
});

由於 valueget, set 爲互斥關係, 因此沒法同時進行定義。code

writeableget, set

var obj = Object.create(null);

Object.defineProperty(obj, 'foo', {
    // 失效
    writeable: false,
    set: function (val) {
        // this 指向 obj
        this._foo = val;
    },
    get: function () {
        return this._foo;
    }
});

obj.foo = 'abc'; // set()
obj._foo === obj.foo  // return true
console.log(obj.foo); // return 'abc'
'foo' in obj // return true

writeable 失效, 沒法對屬性作任何限制

重複定義對象的屬性

var obj = Object.create(null);

Object.defineProperty(obj, 'foo', {
    configurable: false,
    value: 'foo'
});

// Uncaught TypeError: Cannot redefine property: foo
Object.defineProperty(obj, 'foo', {
    value: 'foo2'
});

一旦定義了configurable: false之後, 不容許再次定義 descriptor

獲取對象的屬性描述

var obj = Object.create(null);

Object.defineProperty(obj, 'foo', {
    configurable: true,
    value: 'foo'
});
var descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

console.log(descrptor); // {value: "foo", writable: false, enumerable: false, configurable: false}

// {foo: {value: "foo", writable: false, enumerable: false, configurable: false}}
Object.getOwnPropertyDescriptors(obj);

對象拷貝

淺拷貝

  • Object.assign

將候選的對象裏的可枚舉屬性進行引用賦值, 支持多個候選的對象傳遞

var o = {
  foo: 'foo',
  bar: 'bar'
};

Object.defineProperty(o, 'foo', {
  enumerable: false
});

var obj = Object.assign(Object.create(null), o, {
  o: o
});

/*
obj = {
  bar: 'bar',
  o: {
    'foo': 'foo',
    'bar': 'bar'
  }
}
*/
obj.o === o; // return true;

因爲Object.assign 處於淺拷貝的關係, 因此返回的key 都爲簡單的引用方式.

深度拷貝

使用 對象系列化方式copy數據格式

// 該方法只能拷貝基本數據類型
var obj = {a: 1, b: 2, c: function () { console.log('hello world');}, d: {e: 3, f: 4}};

JSON.parse(JSON.stringify(obj));

自行實現深度拷貝

function deepClone (obj) {
  var newObj;
  var isPlainObject = function (o) {
    return Object.prototype.toString.call(o) === '[object Object]';
  };
  var isArray = function (o) {
    return Object.prototype.toString.call(o) === '[object Array]';
  };
  if (isArray(obj)) {
    newObj = [];
    for (var i = 0, len = obj.length; i < len; i++) {
      newObj.push(deepClone(obj[i]));
    }
  } else if (isPlainObject(obj)) {
    newObj = {};
    for (var key in obj) {
      newObj[key] = deepClone(obj[key]);
    }
  } else {
    newObj = obj;
  }
  return newObj;
}

var o = {a: 1, b: [{c: 2, d: 3}]};
var o2 = deepClone(o);
o.b[0] !== o2.b[0]; // return true

深度拷貝對象函數, 內置的對象和數組都被完整的拷貝出來。

循環引用拷貝問題

var o = {a: 1, b: 2};

// 循環引用問題
o.foo = o;

// Uncaught TypeError: Converting circular structure to JSON
JSON.stringify(o);

// Uncaught RangeError: Maximum call stack size exceeded
deepClone(o);

// 將循環引用的key設置爲不可枚舉型
Object.defineProperty(o, 'foo', {enumerable: false});
// OK {"a":1,"b":2}
JSON.stringify(o);

避免重複循環引用的深度clone

function deepClone (obj) {

  var objStack = [];

  var isPlainObject = function (o) {
    return Object.prototype.toString.call(o) === '[object Object]';
  };

  var isArray = function (o) {
    return Object.prototype.toString.call(o) === '[object Array]';
  };
  
  var isError = function (o) {
    return o instanceof Error;
  };

  function _deepClone (obj) {
    var newObj, cloneObj;
    if (isArray(obj) || isPlainObject(obj)) {
      // 對象重複引用
      if (objStack.indexOf(obj) === -1) {
        objStack.push(obj);
      } else {
        return new Error('parameter Error. it is exits loop reference');
      }
    }
    if (isArray(obj)) {
      newObj = [];
      for (var i = 0, len = obj.length; i < len; i++) {
        cloneObj = _deepClone(obj[i]);
        if (!isError(cloneObj)) {
          newObj.push(cloneObj);
        }
      }
    } else if (isPlainObject(obj)) {
      newObj = {};
      for (var key in obj) {
        cloneObj = _deepClone(obj[key]);
        if (!isError(cloneObj)) {
          newObj[key] = cloneObj;
        }
      }
    } else {
      newObj = obj;
    }
    return newObj;
  }
  return _deepClone(obj);
}

Object.create 建立對象實例

通常建立對象有三種方式

  • 字面量方式 var o = {};
  • 構造函數方式 var o = new Object();
  • Object.create();

Object.create 與 字面量建立的區別

Object.create 能夠指定建立的對象的隱式原型鏈 __proto__, 也能夠建立空原型鏈的的對象

var prototype = {
  foo: 'foo',
  name: 'prototoype'
};
var o = Object.create(prototype);
var o2 = Object.create(null);
o.__proto__ === prototype; // true

'__protot__' in o2; // false

Object.create 與構造函數的區別

Object.create 直接進行開闢對象空間, 綁定隱式原型鏈屬性。而構造函數建立對象除了上面的操做之外, 還會運行構造函數。

利用 Object.create 與構造函數, 實現繼承對象關係

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

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
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.'

相關學習文章

相關文章
相關標籤/搜索