摘要:在構造函數的內部,的指向是新創建的對象。如果構造函數沒有顯式的表達式,則會隱式的返回新創建的對象對象。原型模式在構造函數模式中提到每次之后創建的新的對象是互相獨立的,是獨享的。
1.構造函數模式
JavaScript中的構造函數是通過new調用的,也就是說,通過new關鍵字調用的函數都被認為是構造函數。
在構造函數的內部,this的指向是新創建的對象Object。
如果構造函數沒有顯式的return表達式,則會隱式的返回新創建的對象——this對象。
function Foo () { this.name = "rccoder"; } Foo.prototype.test = function () { console.log (this.name); } var bar = new Foo(); bar.name; // rccoder bar.test(); // rccoder
在構造函數中,顯式的return會影響返回的值,但是僅限于返回的是 一個對象。當返回值不是一個對象的時候,實際上會返回一個新創建的對象;當返回值就是一個對象的時候,返回的不是新創建的對象,而是本身就要返回的那個對象
function Foo () { return 2; } new Foo(); // Foo {} 返回的不是2,而是新創建了一個對象 function Bar () { this.name = "rccoder"; return { foo: 1 } } new Bar(); // Object {foo: 1} 返回要返回的那個對象 (new Bar()).name // undefined (new Bar()).foo // 1
構造函數模式主要有以下幾個特點:
沒有顯式的返回對象
直接將屬性和方法賦值給this對象
沒有return語句
通過new關鍵字調用的函數都被認為是構造函數。
new之后產生的是一個新的對象,可以在new的時候傳遞參數,并且把這個參數的值賦值給this 指針,這樣,傳遞進去的內容,就變成了新產生的對象的屬性或者方法。
為了讓代碼看起來更加的“優雅”,構造函數的首字母都是大寫。
除此之外,用構造函數產生的實例中,他的原型都會默認的包含一個constructor屬性,會指向構造函數。這樣就能夠知道這個對象是從哪兒創建的,也就是說能夠區分這個對象的類型了(下面的工廠模式就無法區分對象的類型)。
function Foo () { this.value = 1; } test = new Foo (); test.constructor == Foo ();
當然,構造函數也是可以直接執行的,而不是一定要new,直接執行的化“構造函數”中的this指向的就不再是新產生的對象了(實際上這種情況下就和普通的函數一樣,并不會產生新的對象),往往在瀏覽器中是window.
構造函數在每次new的時候產生的實例都是重新創建的,因此不同實例上的同名函數是不相等的。
function Foo () { this.test = function () { }; } var a = new Foo(); var b = new Foo(); a.test === b.test // false
所以說,構造函數每次new都是產生一個新的實例,并且這個實例的屬性和方法是獨享的。這樣往往造成了一些浪費(屬性是獨有的可以理解,但是就方法而言,大多數往往是一樣的,這和我們想要的可能有點差別)。
2.工廠模式為了不去使用new關鍵字,上面提到的構造函數必須顯式的返回。當前這個時候就可以理解為不是構造函數了。
function Foo () { var value = 1; return { method: function () { return value; } }; }; Foo.prototype = { foo: function () { } }; new Foo(); Foo();
上面加不加new的返回結果是完全一樣的,都是一個新創建的,擁有method屬性的對象。嗯。如果對閉包有所理解的話,他返回的就是一個閉包!
需要注意的是,上面返回的是一個包含method屬性的自定義對象,所以他并不返回Foo.prototype.
(new Foo()).foo // undefined (Foo()).foo // undefined
按照正常人的思路,一般選擇用new來調用函數總是顯得很奇怪,這也估計就是大多人說不要使用new關鍵字來調用函數的原因了,因為如果忘記new就會產生難以察覺的錯誤。
嗯,是時候引出工廠模式了:
function Foo () { var obj = {}; obj.value = "rccoder"; var privateValue = 2; obj.method = function (value) { this.value = value; }; obj.getPrivate = function () { return privateValue; }; return obj; }
就像上面的代碼一樣,有個工廠,就這樣生產出了一個個的工人。工廠模式解決了多個比較相似的對象重復創建的問題。但是這個創建只單純的一個創建,但并不知道這個對象是從哪里創建的,也就是說無法去區分這個對象的類型。
當然還有一些其他的缺點,比如由于新創建的對象只是簡單的創建,所以不能共享原型上的方法,如果要實現所謂的繼承,就要從另外的一個對象去拷貝所有的屬性...嗯,他放棄了原型,為了去防止new帶來的問題。
3.原型模式在構造函數模式中提到每次new之后創建的新的對象是互相獨立的,是獨享的。
構造函數每次new都是產生一個新的實例,并且這個實例的屬性和方法是獨享的。這樣往往造成了一些浪費(屬性是獨有的可以理解,但是就方法而言,大多數往往是一樣的,這和我們想要的可能有點差別)
就最后一句而言,我們或許可以這樣寫:
function Foo (value) { this.value = value; this.method = method; } function method () { console.log (this.value); }
這樣把方法去放在外面,在構造函數里面去調用這個函數,好像就hack的解決了上面的問題。但是這個函數好像就是全局函數了,并且和Foo()看上去并不怎么愉快的是一家人,談封裝也就有些牽強。
這種去共享方法的問題用prototype看似就可以解決,畢竟他產生的屬性和方法是所有產生的實例所共享的。
function Foo () { ... }; Foo.prototype.value = "rccoder"; Foo.prototype.method = function () { console.log (this.value); }; var test = new Foo (); test.method(); // rccoder
這樣看起來好像是可行的,當需要找某個對象的屬性的時候,往往直接看有沒有這個屬性,沒有的話再按照原型鏈向上尋找,而不是去尋找構造函數。
原型是動態的,所以不要隨便的去修改原型。這個修改后會立即影響實例的結果。
如果我們有Foo.prototype = Bar.prototype?的寫法,改變這兩個對象任何一個的原型都會影響另外一個,這在大多的情況下是不可取的。
一般情況下不建議對原型做修改,因為很可能由于代碼量太多導致維護太困難。
另外,還記得用原型模式的初衷嗎?是要公用方法,而不是公用屬性。純粹的用原型會同樣的公用屬性,這在很多情況下看起來是很郁悶的。所以可能需要我們把原型和構造函數結合起來使用。
4.優雅混合使用構造函數與原型這或許是比較理想話的使用方法了,用構造函數來區分獨享的屬性,用原型來共享大家都用的方法。
function Foo (value1, value2) { this.value1 = value1; this.value2 = value2; } Foo.prototype.method = function () { console.log (this.value1) }; test1 = new Foo (2, 3); test1.method(); // 2 test2 = new Foo (4, 5); test2.method() // 4
哦,對了,你可能會看見這樣寫上面的代碼
function Foo (value1, value2) { this.value1 = value1; this.value2 = value2; } Foo.prototype = { constructor: Foo, method: function () { console.log (this.value1); } } test = new Foo (2, 3); test.method();
別怕,這只是覆蓋了Foo的原型而已,是真的覆蓋到連constructor是誰都不認識了,所以需要手動的是想一下,指向誰呢?正常人的話指向的應該是構造函數吧。
參考資料:
JavaScript密碼花園
JavaScript 原型理解與創建對象應用-于江水的博客
原文鏈接:http://life.rccoder.net/javascript/1216.html
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78363.html
摘要:下面,讓我們以一個函數的創建和激活兩個時期來講解作用域鏈是如何創建和變化的。這時候執行上下文的作用域鏈,我們命名為至此,作用域鏈創建完畢。 JavaScript深入系列第五篇,講述作用鏈的創建過程,最后結合著變量對象,執行上下文棧,讓我們一起捋一捋函數創建和執行的過程中到底發生了什么? 前言 在《JavaScript深入之執行上下文棧》中講到,當JavaScript代碼執行一段可執行代...
摘要:深入系列第七篇,結合之前所講的四篇文章,以權威指南的為例,具體講解當函數執行的時候,執行上下文棧變量對象作用域鏈是如何變化的。前言在深入之執行上下文棧中講到,當代碼執行一段可執行代碼時,會創建對應的執行上下文。 JavaScript深入系列第七篇,結合之前所講的四篇文章,以權威指南的demo為例,具體講解當函數執行的時候,執行上下文棧、變量對象、作用域鏈是如何變化的。 前言 在《Jav...
摘要:之運算符運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。使用指定的參數調用構造函數,并將綁定到新創建的對象。 JavaScript之new運算符 new運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。new關鍵字會進行如下的操作: 1. 創建一個空的簡單JavaScript對象(即{}); 2. 鏈接該對象(即設置該對象的構造函數)到另一個對...
摘要:深入系列第四篇,具體講解執行上下文中的變量對象與活動對象。下一篇文章深入之作用域鏈本文相關鏈接深入之執行上下文棧深入系列深入系列目錄地址。 JavaScript深入系列第四篇,具體講解執行上下文中的變量對象與活動對象。全局上下文下的變量對象是什么?函數上下文下的活動對象是如何分析和執行的?還有兩個思考題幫你加深印象,快來看看吧! 前言 在上篇《JavaScript深入之執行上下文棧》中...
摘要:在最開始的時候,原型對象的設計主要是為了獲取對象的構造函數。同理數組通過調用函數通過調用原型鏈中描述了原型鏈的概念,并將原型鏈作為實現繼承的主要方法。 對象的創建 在JavaScript中創建一個對象有三種方式。可以通過對象直接量、關鍵字new和Object.create()函數來創建對象。 1. 對象直接量 創建對象最直接的方式就是在JavaScript代碼中使用對象直接量。在ES5...
摘要:深入系列第十四篇,講解創建對象的各種方式,以及優缺點。也就是說打著構造函數的幌子掛羊頭賣狗肉,你看創建的實例使用都無法指向構造函數這樣方法可以在特殊情況下使用。 JavaScript深入系列第十四篇,講解創建對象的各種方式,以及優缺點。 寫在前面 這篇文章講解創建對象的各種方式,以及優缺點。 但是注意: 這篇文章更像是筆記,因為《JavaScript高級程序設計》寫得真是太好了! 1....
閱讀 1201·2021-11-24 11:16
閱讀 3428·2021-11-15 11:38
閱讀 1920·2021-10-20 13:47
閱讀 546·2021-09-29 09:35
閱讀 2192·2021-09-22 15:17
閱讀 1013·2021-09-07 09:59
閱讀 3374·2019-08-30 13:21
閱讀 2904·2019-08-30 12:47