Object.create( )


知識點:

  • Object.create( ) 的用法,
  • 原型繼承,
  • Object.assign() 的區別,
  • Object.getPrototypeOf() 獲取原型
  • Object.getOwnPropertyDescriptors() 獲取對象可枚舉的屬性

小結:

1、Object.create() 是把現有對象的屬性,掛到新建對象的原型上,新建對象為空對象

2、Object.create() 的第二個參數,為添加的可枚舉屬性(即自身屬性,不是原型上的),例子見下文第二點

 

可以把Object.create()的參數理解為:第一個參數是放在新對象的原型上的,第二個參數是放在新對象的實例上的。

Object.getOwnPropertyDescriptors() 得到是對象自身的可枚舉屬性

 

target = { a: 1, b: 2, c: 3 };
Object.getOwnPropertyDescriptors(target) // a: 1, b: 2, c: 3

 

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"}
Object.create()方法創建的對象時,屬性是在原型下面的,也可以直接訪問 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__ 屬性

JavaScript 的對象繼承是通過原型鏈實現的。ES6 提供了更多原型對象的操作方法。
__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.getOwnPropertyDescriptorsObject.assign()Object.createObject.getPrototypeOf()方法,通常這幾種方法都有一起結合使用。
如果上面的例子還不理解,這里把他簡單的拿到 對象的繼承 來講解。理解的話就可以忽略啦。


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM