在javascript中, 數據類型主要分爲原始類型和引用類型兩種。而一切引用類型都來自於Object的拷貝。全部引用類型的原型鏈均可以追溯到 Objectjavascript
JavaScript
內置的一些構造函數有 Object
, Function
, Number
, String
, Boolean
, Array
, RegExp
等等, 它們主要有兩個共有的屬性。java
測試各個數據類型的引用狀況數組
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__
, 它負責控制對象的原型屬性和方法的查詢使用函數
// 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
// 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
屬性描述通常是由enumrable
, value
, get
, set
, configurable
, writeable
, value
組成, 其中 get
, set
與 value
爲互斥關係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
值value
與 get
, 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; } });
由於 value
與 get
, set
爲互斥關係, 因此沒法同時進行定義。code
writeable
與 get
, 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);
將候選的對象裏的可枚舉屬性進行引用賦值, 支持多個候選的對象傳遞
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
建立對象實例通常建立對象有三種方式
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.'