JavaScript創建對象的幾種方式及關於this指向問題
工廠模式
工廠模式一般用於抽象創建特定對象的過程,是按照特定接口創建對象的方式。
function createPerson(name, age) { let o = {}; o.name = name; 0.age = age; o.study = function() { console.log(`${this.name} is studying`) } return o } const p1 = createPerson('張三', 18) const p2 = createPerson('李四', 20) p1.study() // 張三 is studying
值得一提的是,如果在 createPerson
函數中,o.study
改為箭頭函數,那麼打印的 this 會發生改變,這是因為箭頭函數發生瞭 this
指向的問題。
工廠模式的優點: 在 createPerson
中,可以用不同的參數多次調用工廠函數,每次都會返回包含特定參數和一個方法的對象。可以解決創建多個類似對象的問題。
工廠模式的缺點:
- 1. 沒有解決對象標識問題。
- 2. 每次創建對象,都會創建一個公共的方法,而方法也是對象,所以造成內存浪費。
構造函數模式
在 JS 中, 構造函數是用於創建特定類型對象的。像 Object
、Array
這樣的原生構造對象,運行時,可以直接在執行環境中運行。當然也可以自定義構造函數,以函數的形式為自己的對象類型定義屬性和方法。
function Student(name, age) { this.name = name; this.age = age; this.study = function() { console.log(`${this.name} is studying`) } } const p1 = new Student('張三', 18) const p2 = new Student('李四', 20)
通過上面的代碼,可以實現和工廠模式一樣的效果。但是值得註意的是: 如果這裡的 study
函數變為箭頭函數, this
的指向是不會發生變化的。
如同上面的代碼,如果調用構造函數,就必須使用 new
關鍵字,在這個過程中,構造函數會做四件事情:
- 在內存中創造一個新的對象
- 將這個新的對象的隱式原型
__proto__
賦值為該構造函數的顯示原型prototype
- 構造函數的內部的
this
被賦值為這個新的對象(給新對象添加新的屬性) - 執行構造函數中的代碼,並且判斷有無對象返回值,如果存在,則返回這個返回值,否則返回這個新的對象
- 構造函數的優缺點: 首先,構造函數解決瞭對象的標識問題,但是如果構造函數中存在著函數,那麼每次創建一個實例,就相當於函數也被重新創造瞭一遍,因為在JS中函數也是相當於對象,造成瞭對空間的浪費。
關於 this
在標準函數中, this 引用的是把函數當成方法調用的上下文對象,這個時候常稱其為 this。
我們在上文說過,工廠模式和構造函數模式,其內部如果出現箭頭函數,那麼 this
的指向會出現不同,這是因為,箭頭函數中不存在 this
, 箭頭函數中的 this
, 會保留定義該函數的上下文,this
到底引用哪個對象必須到函數調用時,才會確定, 在工廠模式時, 雖然通過p1.study()
,進行調用,但是由於箭頭函數內部沒有 this
,所以向上查找,找到function createPerson
函數, 綁定 window
, 而在構造函數模式中this 賦值給瞭這個新的對象,相當於箭頭函數的this
, 就是這個新的對象,也就是之後的實例。
可能,通過上面的描述,很多同學仍然對下面箭頭函數中的this,會保留定義該函數的上下文不太理解,我列舉一個例子。
// 工廠模式 function createPerson(name, age) { let o = {}; o.name = name; o.age = age; o.study = () => { console.log(`${this.name} is studying`) } return o } const obj = { name: '張三' } const p1 = createPerson.call(obj, '李四', 20) // createPerson 綁定 obj, 那麼箭頭函數也會綁定 obj p1.study() // 所以這裡的輸出是 張三 is studying,而不是 undefined
原型模式
每個函數都會創建一個
prototype
屬性,這個屬性是一個對象,包含應該由特定引用類型的實例共享的屬性和方法。實際上,這個對象就是通過調用構造函數創建的對象的原型。使用原型對象的好處是,在它上面定義的屬性和方法可以被對象實例共享。原來在構造函數中直接賦值給對象實例的值,可以直接賦值給他們的原型。
function Student() {} Student.prototype.name = '張三'; Student.prototype.age = 20; Student.prototype.friends = ['ls', 'ww']; Student.prototype.study = function() { console.log(`${this.name} is studying`) } const p1 = new Student() const p2 = new Student()
通過上面創建的p1
, p2
,共用瞭Student
原型上的屬性,相當於創造瞭兩個擁有相同屬性的不同對象。同理,如果上面的 study
改為瞭箭頭函數,大傢應該也能知道 this
綁定的是誰瞭吧。(Tip: new 和 顯示綁定不可以同時使用)
原型模式的優缺點 : 首先原型模式解決瞭上兩種模式造成的空間浪費問題,但是,其屬性都是共享的,所以一般來說,原型模式不會單獨去使用。
推薦閱讀:
- 一起來瞭解JavaScript面向對象
- Javascript的原型和原型鏈你瞭解嗎
- JavaScript原型鏈及常見的繼承方法
- JavaScript構造函數與原型之間的聯系
- JavaScript中的this例題實戰總結詳析