摘要:工廠函數(shù)創(chuàng)建實(shí)例同時(shí)也面臨實(shí)例類(lèi)型的問(wèn)題返回的對(duì)象是構(gòu)造函數(shù)的實(shí)例為什么實(shí)例函數(shù)不相等呢在中是一種引用類(lèi)型。使用指定的參數(shù)調(diào)用構(gòu)造函數(shù),并將綁定到新創(chuàng)建的對(duì)象。構(gòu)造函數(shù)返回的對(duì)象就是表達(dá)式的結(jié)果。
有了前面幾節(jié)的知識(shí),這一節(jié)我們理解起來(lái)就要輕松很多。在 javascript 里函數(shù)也是對(duì)象,瀏覽器的全局上下文也是對(duì)象, key - value 的身影在代碼里比較常見(jiàn),合理的使用對(duì)象多維度、可擴(kuò)展的特性可以為開(kāi)發(fā)中帶來(lái)很多樂(lè)趣。
如果知識(shí)存在盲區(qū),則實(shí)際開(kāi)發(fā)中就會(huì)就會(huì)應(yīng)為評(píng)估不足,模型設(shè)計(jì)不合理出現(xiàn)各種問(wèn)題, 小則打打補(bǔ)丁、模塊API重新設(shè)計(jì),做兼容處理。 大則是關(guān)鍵數(shù)據(jù)維度無(wú)法滿(mǎn)足應(yīng)用場(chǎng)景, 就需要費(fèi)事費(fèi)力的進(jìn)行架構(gòu)調(diào)整或者重構(gòu)了。
下面我們來(lái)梳理一下 javascript 對(duì)象的表現(xiàn)方式和特點(diǎn),過(guò)于細(xì)節(jié)的知識(shí)就不梳理了。
JavaScript 的設(shè)計(jì)是一個(gè)簡(jiǎn)單的基于對(duì)象的范式。一個(gè)對(duì)象就是一系列屬性的集合,一個(gè)屬性包含一個(gè)屬性名和一個(gè)屬性值。一個(gè)屬性的值可以是函數(shù),這種情況下屬性也被稱(chēng)為方法。除了瀏覽器里面預(yù)定義的那些對(duì)象之外,我們也可以定義自己的對(duì)象。熟悉 javascript 的語(yǔ)法特性,合理的設(shè)計(jì)數(shù)據(jù)模型,創(chuàng)建靈活、不含糊的自定義對(duì)象能夠提高 javascript 的運(yùn)行效率。字面量對(duì)象
使用字面量方式創(chuàng)建對(duì)象占據(jù)了大多數(shù)開(kāi)發(fā)場(chǎng)景,字面量對(duì)象示例:
let foo = { a: 1, b: "1234", c: function () { console.log(this.a + this.b) } } let foo1 = { a: 666, b: "hi", c: function () { console.log(`${this.b}, ${this.a}`) } } foo.c() // "11234" foo1.c() // "hi, 666"
對(duì)象字面量的特點(diǎn)主要是直觀、簡(jiǎn)單靈活,每一個(gè)key、value在編碼階段就是確定的。
使用對(duì)象字面量的方式來(lái)創(chuàng)建對(duì)象的缺點(diǎn)是,當(dāng)我們需要?jiǎng)?chuàng)建多個(gè)相同對(duì)象時(shí)必須為每個(gè)對(duì)象在源代碼中編寫(xiě)變量和方法。當(dāng)這樣的相同內(nèi)容的對(duì)象很多時(shí)就是一場(chǎng)災(zāi)難。于是我們發(fā)明了很多其他創(chuàng)建對(duì)象的方式,下面進(jìn)一步探討。
工廠模式工廠模式創(chuàng)建對(duì)象示例:
let createFoo = function (a, b, c) { let o = new Object() o.a = a o.b = b o.c = c return o } let foo = createFoo(1, "1234", function(){ console.log(this.a + this.b) }) let foo1 = createFoo(666, "hi", function(){ console.log(`${this.b}, ${this.a}`) }) foo.c() // "11234" foo1.c() // "hi, 666"
所謂工廠模式就是對(duì)象的創(chuàng)建就像"商品"通過(guò)工廠按照標(biāo)準(zhǔn)化的流程被加工出來(lái)。
上面就是一個(gè)工廠函數(shù)的栗子,執(zhí)行 createFoo 函數(shù)時(shí)先創(chuàng)建一個(gè)對(duì)象 o,然后把傳遞進(jìn)來(lái)的實(shí)參添加到 o 上面,最后返回對(duì)象 o。這樣每次執(zhí)行 createFoo 函數(shù)都會(huì)返回一個(gè)新的對(duì)象,當(dāng)我們需要1000個(gè)相似對(duì)象時(shí) createFoo 就為我們?cè)趦?nèi)部生成了1000個(gè)獨(dú)立的對(duì)象 o。通過(guò)對(duì)這個(gè)栗子的分析會(huì)發(fā)現(xiàn): 工廠函數(shù)在進(jìn)行大批量對(duì)象創(chuàng)建時(shí)對(duì)資源的消耗比較大,同時(shí)由于每次都返回的是一個(gè)新對(duì)象,我們就沒(méi)辦法判斷對(duì)象的類(lèi)型。
工廠函數(shù)與字面量方式創(chuàng)建對(duì)象相比,優(yōu)勢(shì)就是不用在編碼階段創(chuàng)建大批量相似結(jié)構(gòu)的對(duì)象,而這一系列的創(chuàng)建工作都是在運(yùn)行階段創(chuàng)建的。每次創(chuàng)建實(shí)例時(shí)都要?jiǎng)?chuàng)建實(shí)例對(duì)應(yīng)的所有屬性和方法,所以工廠函數(shù)同樣存在創(chuàng)建N個(gè)實(shí)例需要?jiǎng)?chuàng)建N個(gè)屬性、方法的問(wèn)題。
工廠函數(shù)創(chuàng)建實(shí)例同時(shí)也面臨實(shí)例類(lèi)型的問(wèn)題:
foo instanceof createFoo // false foo1 instanceof createFoo // false // 返回的對(duì)象是構(gòu)造函數(shù) Object 的實(shí)例 foo instanceof Object // true foo1 instanceof Object // true
為什么實(shí)例函數(shù)不相等呢?
在 JavaScript 中 objects 是一種引用類(lèi)型。兩個(gè)獨(dú)立聲明的對(duì)象永遠(yuǎn)也不會(huì)相等(因?yàn)樽兞?foo 和 foo1 指向的堆地址不同),即使他們有相同的屬性,只有在比較一個(gè)對(duì)象和這個(gè)對(duì)象的引用時(shí),才會(huì)返回true.
let too = { a: 1 } let too1 = { a: 1 } let too2 = too1 too == too1 // false too === too1 // false too1 == too2 // true too1 ===too2 // true構(gòu)造函數(shù)
構(gòu)造函數(shù)方式創(chuàng)建自定義對(duì)象,就是利用函數(shù)中構(gòu)造函數(shù)、原形、實(shí)例對(duì)象之間的關(guān)系來(lái)封裝私有屬性、公有屬性:
function Foo (a, b, c) { this.a = a this.b = b this.c = c } let foo1 = new Foo(1, "1234", function(){ console.log(this.a + this.b) }) let foo2 = new Foo(666, "hi", function(){ console.log(`${this.b}, ${this.a}`) }) // foo1、foo2 是 Foo 的實(shí)例 foo1 instanceof Foo // true foo2 instanceof Foo // true
構(gòu)造函數(shù)的實(shí)現(xiàn)看著要簡(jiǎn)單很多,也能通過(guò)實(shí)例判斷出類(lèi)型。
構(gòu)造函數(shù)的執(zhí)行邏輯:
構(gòu)造函數(shù)初始化階段首先會(huì)向上下文棧中壓入一個(gè)上下文,接著在變量對(duì)象創(chuàng)建的時(shí)候會(huì)收集實(shí)參,初始化函數(shù)內(nèi)部的變量申明、確定 this 的指向、確定作用鏈。將實(shí)參的值分別拷貝給變量a、b、c。然后像普通函數(shù)一樣進(jìn)入執(zhí)行階段,執(zhí)行函數(shù)內(nèi)部語(yǔ)句.
構(gòu)造函數(shù)就是函數(shù) 既然構(gòu)造函數(shù)就是普通函數(shù), 那么為什在函數(shù)前面加一個(gè) new 就能實(shí)例化并返回一個(gè)對(duì)象呢?
我們來(lái)創(chuàng)建一個(gè)模擬構(gòu)造函數(shù)加深理解,沒(méi)錯(cuò)是創(chuàng)建一個(gè)構(gòu)造函數(shù)(思路來(lái)源于網(wǎng)絡(luò), 無(wú)恥的偷過(guò)來(lái)了?????)。
// 假設(shè)我們創(chuàng)建一個(gè)汽車(chē)對(duì)象類(lèi)型, car函數(shù) function Car(make, model, year) { this.make = make this.model = model this.year = year this.drive = function (name) { console.log(`${name} drives the ${this.model} ${this.make}`) } } // 將函數(shù)以參數(shù)形式傳入 function New(func) { // 聲明一個(gè)中間對(duì)象,該對(duì)象為最終返回的實(shí)例 let res = {} if (func.prototype !== null) { // 將實(shí)例的原型指向構(gòu)造函數(shù)的原型 res.__proto__ = func.prototype } // ret為構(gòu)造函數(shù)執(zhí)行的結(jié)果,這里通過(guò)apply,將構(gòu)造函數(shù)內(nèi)部的this指向修改為指向res,即為實(shí)例對(duì)象 var ret = func.apply(res, Array.prototype.slice.call(arguments, 1)) // 當(dāng)我們?cè)跇?gòu)造函數(shù)中明確指定了返回對(duì)象時(shí),那么new的執(zhí)行結(jié)果就是該返回對(duì)象 if ((typeof ret === "object" || typeof ret === "function") && ret !== null) { return ret } // 如果沒(méi)有明確指定返回對(duì)象,則默認(rèn)返回res,這個(gè)res就是實(shí)例對(duì)象 return res } // 通過(guò)new聲明創(chuàng)建實(shí)例,這里的p1,實(shí)際接收的正是new中返回的res let mycar = New(Car, "Tesla", "Model X", 2018) mycar.drive("小丸子") console.log(mycar.make); // mycar 是 Car 的實(shí)例 mycar instanceof Car // true
將 let mycar = new Car(...) 實(shí)例化對(duì)象的方式看作是let mycar = New(Car, "Tesla", "Model X", 2018) 的一種簡(jiǎn)單的語(yǔ)法糖寫(xiě)法。
代碼 new Car(...) 執(zhí)行時(shí),會(huì)發(fā)生以下事情:
一個(gè)繼承自 Car.prototype 的新對(duì)象被創(chuàng)建。
使用指定的參數(shù)調(diào)用構(gòu)造函數(shù) Car ,并將 this 綁定到新創(chuàng)建的對(duì)象。new Car 等同于 new Car(),也就是沒(méi)有指定參數(shù)列表,Car 不帶任何參數(shù)調(diào)用的情況。
構(gòu)造函數(shù)返回的對(duì)象就是 new 表達(dá)式的結(jié)果。如果構(gòu)造函數(shù)沒(méi)有顯式返回一個(gè)對(duì)象,則使用步驟1創(chuàng)建的對(duì)象。(一般情況下,構(gòu)造函數(shù)不返回值,但是用戶(hù)可以選擇主動(dòng)返回對(duì)象,來(lái)覆蓋正常的對(duì)象創(chuàng)建步驟)
實(shí)例類(lèi)型無(wú)法判斷的問(wèn)題, 通過(guò)構(gòu)造函數(shù)的方式來(lái)創(chuàng)建對(duì)象完美的解決了。但是構(gòu)造器函數(shù)存在和工廠函數(shù)一樣的問(wèn)題:每次創(chuàng)建一個(gè)實(shí)例對(duì)象時(shí)都會(huì)在內(nèi)部新建一個(gè)中間對(duì)象,實(shí)例方法也會(huì)創(chuàng)建N次,這樣就存在不必要的內(nèi)層消耗。
原型與構(gòu)造函數(shù)組合在上面Car構(gòu)造函數(shù)的栗子中,當(dāng)創(chuàng)建100個(gè) Car 的實(shí)例時(shí)內(nèi)部復(fù)制了100次 drive 函數(shù)。 雖然每個(gè) drive 函數(shù)的功能一樣,但是由于分別屬于不同的實(shí)例就每次都分配獨(dú)立的內(nèi)存空間。
相同的功能函數(shù)怎么忍受得了重復(fù)創(chuàng)建。回憶之前我們?cè)?b>原型一節(jié)講到的,每個(gè)函數(shù)存在prototype 屬性,通過(guò)該屬性指向自己的原型對(duì)象。那我們可以在函數(shù)的原型上做文章,將實(shí)例公共的屬性和方法掛載在原型上。實(shí)例通過(guò)__ptoto__屬性指向了構(gòu)造函數(shù)的原型,從而讓構(gòu)造函數(shù)的原型對(duì)象在各個(gè)實(shí)例的原型鏈上,于是我們通過(guò)構(gòu)造函數(shù)的原型來(lái)實(shí)現(xiàn)公有屬性和方法的封裝,且只會(huì)創(chuàng)建一次。
還是上面 Car的栗子:
function Car(make, model, year) { this.make = make this.model = model this.year = year } Car.prototype.drive = function (name) { console.log(`${name} drives the ${this.model} ${this.make}`) } let mycar = new Car( "Tesla", "Model X", 2018) mycar.drive("小丸子")
上面的栗子也還可以寫(xiě)成這樣子:
function Car(make, model, year) { this.make = make this.model = model this.year = year } Car.prototype = { constructor: Car, drive: function () { console.log(`${name} drives the ${this.model} ${this.make}`) } } let mycar = new Car( "Tesla", "Model X", 2018) mycar.drive("小丸子")
兩種寫(xiě)法是等價(jià)的,需要注意的是后一種相當(dāng)于創(chuàng)建一個(gè)新對(duì)象并賦值給了構(gòu)造函數(shù)Car的原型,如果不將新原型的constructor重現(xiàn)指向構(gòu)造函數(shù),則會(huì)導(dǎo)致構(gòu)造函數(shù)Car的實(shí)例類(lèi)型判斷出錯(cuò)(instanceof Car 為 false).
不同的實(shí)現(xiàn)方法都有各自的使用場(chǎng)景。同時(shí)對(duì)象的實(shí)現(xiàn)方式又與數(shù)據(jù)維度以及另外一個(gè)話題 設(shè)計(jì)模式有關(guān)。我們使用原型與構(gòu)造函數(shù)組合模式就能夠解決很多問(wèn)題。
關(guān)于 javascript 的各種模式可以參考:
Javascript設(shè)計(jì)模式
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107884.html
摘要:講清楚之參數(shù)傳值參數(shù)傳值是指函數(shù)調(diào)用時(shí),給函數(shù)傳遞配置或運(yùn)行參數(shù)的行為,包括通過(guò)進(jìn)行傳值。所以對(duì)的賦值會(huì)改變上下文棧中標(biāo)識(shí)符保存的具體值此時(shí)如果使用的是按引用傳遞,則變量所指向的對(duì)象因該也被賦值為。 講清楚之 javascript 參數(shù)傳值 參數(shù)傳值是指函數(shù)調(diào)用時(shí),給函數(shù)傳遞配置或運(yùn)行參數(shù)的行為,包括通過(guò)call、apply 進(jìn)行傳值。 在實(shí)際開(kāi)發(fā)中,我們總結(jié)javascript參數(shù)傳...
摘要:棧底為全局上下文,棧頂為當(dāng)前正在執(zhí)行的上下文。位于棧頂?shù)纳舷挛膱?zhí)行完畢后會(huì)自動(dòng)出棧,依次向下直至所有上下文運(yùn)行完畢,最后瀏覽器關(guān)閉時(shí)全局上下文被銷(xiāo)毀。 講清楚之執(zhí)行上下文 標(biāo)簽 : javascript 什么是執(zhí)行上下文? 當(dāng) JavaScript 代碼執(zhí)行一段可執(zhí)行代碼時(shí),會(huì)創(chuàng)建對(duì)應(yīng)的上下文(execution context)并將該上下文壓入上下文棧(context stack...
摘要:構(gòu)造函數(shù)和實(shí)例都通過(guò)屬性指向了原形。代碼示例是構(gòu)造函數(shù)的實(shí)例的屬性與的屬性保存的值相等,即他們指向同一個(gè)對(duì)象原形。 講清楚之javascript原型 標(biāo)簽: javascript javascript 中原形是一個(gè)比較難于理解的概念。javascript 權(quán)威指南在原形這一章也花了大量的篇幅進(jìn)行介紹,也許你已經(jīng)讀過(guò)javascript 權(quán)威指南,或者已經(jīng)是讀第N篇了,然而這篇文章的目...
閱讀 2553·2023-04-26 00:57
閱讀 924·2021-11-25 09:43
閱讀 2228·2021-11-11 16:55
閱讀 2241·2019-08-30 15:53
閱讀 3604·2019-08-30 15:52
閱讀 1471·2019-08-30 14:10
閱讀 3388·2019-08-30 13:22
閱讀 1221·2019-08-29 11:18