知識點:
- Object.create( ) 的用法,
- 原型繼承,
- Object.assign() 的區別,
- Object.getPrototypeOf() 獲取原型
- Object.getOwnPropertyDescriptors() 獲取對象可枚舉的屬性
小結:
1、Object.create() 是把現有對象的屬性,掛到新建對象的原型上,新建對象為空對象
2、Object.create() 的第二個參數,為添加的可枚舉屬性(即自身屬性,不是原型上的),例子見下文第二點
可以把Object.create()的參數理解為:第一個參數是放在新對象的原型上的,第二個參數是放在新對象的實例上的。
Object.create(proto, [propertiesObject])
//方法創建一個新對象,使用現有的對象來提供新創建的對象的proto。
用我理解的話來說,就是把現有對象的屬性,掛到新建對象的原型上(__proto__指向原型),新建的對象享有現有對象的屬性。
案例說明:
創建對象的方式不同
// new Object() 方式創建 var a = { rep : 'apple' } var b = new Object(a) console.log(b) // {rep: "apple"} console.log(b.__proto__) // {} console.log(b.rep) // {rep: "apple"} // Object.create() 方式創建 var a = { rep: 'apple' } var b = Object.create(a) console.log(b) // {} console.log(b.__proto__) // {rep: "apple"} console.log(b.rep) // {rep: "apple"}
是它通過原型鏈 proto來訪問到b的值。
創建對象屬性的性質不同
// 創建一個以另一個空對象為原型,且擁有一個屬性p的對象 o = Object.create({}, { p: { value: 42 } }) // 省略了的屬性特性默認為false,所以屬性p是不可寫,不可枚舉,不可配置的: o.p = 24 o.p //42 o.q = 12 for (var prop in o) { console.log(prop) } //"q" delete o.p //false
Object.create() 用第二個參數來創建非空對象的屬性描述符默認是為false的,而構造函數或字面量方法創建的對象屬性的描述符默認為true。看下圖解析:
創建空對象時,對象沒有原型屬性
當用構造函數或對象字面量方法創建空對象時,對象時有原型屬性的,即有_proto_
;
當用Object.create()方法創建空對象時,對象是沒有原型屬性的。
__proto__
屬性
__proto__
屬性(前后各兩個下划線),用來讀取或設置當前對象的prototype對象。目前只有瀏覽器環境必須部署有這個屬性,其他運行環境不一定要部署,因此不建議使用這個屬性,而是使用下面這些來
Object.setPrototypeOf()
(寫操作)、
Object.getPrototypeOf()
(讀操作)、
Object.create()
(生成操作)代替。
var proto = { y: 20, z: 40, showNum(){} }; var o = Object.create(proto);
如果是不用Object,create()方法,我們是如何給對象原型添加屬性和方法的?
------ 通過構造函數或者類,例如:
//創建一個構造函數或者類 var People = function(){} People.prototype.y = 20 People.prototype.showNum = function() {} //通過構造函數創建實例 var p = new People(); console.log(p.__proto__ === People.prototype) // true
現在有 Object.create() 就簡單的多了
Object.setPrototypeOf
__proto__
相同,用來設置一個對象的
prototype
對象,返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。
格式:Object.setPrototypeOf(object, prototype)
var proto = { y: 20, z: 40 }; var o = { x: 10 }; Object.setPrototypeOf(o, proto);
obj.__proto__ = proto;
Object.getPrototypeOf()
描述:用於讀取一個對象的原型對象;
格式:Object.getPrototypeOf(obj);
Object.getPrototypeOf('foo') === String.prototype // true Object.getPrototypeOf(true) === Boolean.prototype // true
[1] 原型屬性的繼承
這里結合一個例子來說說這幾個方法的使用:
場景:拷貝一個構造函數的實例。
var triangle = {a: 1, b: 2, c: 3}; function ColoredTriangle() { this.color = 'red'; } //ColoredTriangle.prototype = triangle; //ColoredTriangle.prototype.constructor === ColoredTriangle// false Object.assign(ColoredTriangle.prototype, triangle) //ColoredTriangle.prototype.constructor === ColoredTriangle// true var c = new ColoredTriangle();
打印出 實例c 看看結構是怎樣的
其中 color 屬性在實例上,而其他的原型上。
現在來拷貝一個 實例 c2
var c2 = Object.assign({},c) console.log(c2.color); //red console.log(c2.a); //undefined
因為 Object.assign() 是不能拷貝到繼承或原型上的方法的。所以 實例c2 沒有 a 這個屬性。那要怎么要才能拷貝到原型上的方法呢?
var triangle = { a: 1, b: 2, c: 3 } function Colored(){ this.color = 'red' } Object.assign(Colored.prototype,triangle) var c = new Colored() //Object.assign 無法將現有對象原型對象拷貝到新的對象 var c2 = Object.assign({},c) //使用 Object.assign() 實現拷貝到原型 //下面實現 c2 拷貝實例對象c 上的屬性和原型對象 //先講講思路,獲取c的原型對象,因為assgin不會拷貝原型的東西,c2 = Object.assign(c.prototype,c) //以下方法思路基本相同 //方法一: var originProto = Object.getPrototypeOf(c)//獲取到實例 c 的原型 //originProto2想當於一個中間值,將 originProto 的原型添加上去 var originProto2 = Object.create(originProto) //如果這里直接 拷貝實例 c 的原型上的東西,則會發現 第一層原型為 color,第二層才為{a:1,b:2,c:3} // var originProto3 = Object.create(c) var c2 = Object.assign(originProto2,c) //根據上面的思路,我寫了一個方法二 //方法二: var c3 = {} c3.__proto__ = c.__proto__ c3 = Object.assign(c3,c) console.log(c3.color) // red console.log(c3.a) // 1 //方法三(推薦)(淺拷貝): var c = new Colored() var c4 = Object.create(Object.getPrototypeOf(c), Object.getOwnPropertyDescriptors(c)) //Object.getOwnPropertyDescriptors(Obj) 返回所指定對象的所有自身屬性的描述符,如果沒有任何自身屬性,則返回空對象。 console.log(Object.getOwnPropertyDescriptors(c)) //{color:red} console.log(c4)
可以把Object.create()的參數理解為:第一個參數是放在新對象的原型上的,第二個參數是放在新對象的實例上的。
Object.getPrototypeOf() 得到的是 c 對象的原型,然后作為第一個參數,所以會在新對象c4的原型上。
Object.getOwnPropertyDescriptors() 得到是 c 對象自身的可枚舉屬性,作為第二個參數,放在 c4 的實例上。
為什么說推薦這個方法呢?因為Object.assign() 方法不能正確拷貝 get ,set 屬性。
例如,我們給 c 實例添加一個 "colorGet" 屬性,並設置該屬性的get 描述符:
var c = new ColoredTriangle(); Object.defineProperty(c,'colorGet', { enumerable: true, // 設為可枚舉,不然 Object.assign 方法會過濾該屬性 get(){ return "Could it return " + this.color } }); var c3 = Object.assign(Object.create(Object.getPrototypeOf(c)), c)

這里沒有拷貝到 "colorGet" 的 get 描述符,而是直接把獲取到的值賦值給 "colorGet" 。
那對於 get 描述符要怎么獲取呢? Object.getOwnPropertyDescriptors就專為解決這問題而生。
而又因為要拷貝原型上的屬性,所以結合Object.create、Object.getPrototypeOf 方法一起使用。即上面的第二種實現方法,如下:
var c = new ColoredTriangle(); Object.defineProperty(c,'colorGet', { enumerable: true, // 設為可枚舉,不然 Object.assign 方法會過濾該屬性 get(){ return "Could it return " + this.color } }); var c3 = Object.create(Object.getPrototypeOf(c), Object.getOwnPropertyDescriptors(c));
此時已經成功的拷貝到了get描述符啦。
雖然說實際開發上很少會要去修改 get 描述符,但是知道多一種方法,遇到這種情況時就知道該怎么去解決了。
注意:這些都只是一個層級的深拷貝。
Object.getOwnPropertyDescriptors
、
Object.assign()
、
Object.create
、
Object.getPrototypeOf()
方法,通常這幾種方法都有一起結合使用。
如果上面的例子還不理解,這里把他簡單的拿到 對象的繼承 來講解。理解的話就可以忽略啦。