摘要:原文如果按面向對象的思路去講的,還是很難去理解,我們可以從另一個方向去理解一下它。
原文:https://legacy.ofcrab.com/press/javascript-new.html
如果按面向對象的思路去講 JavaScript 的 new,還是很難去理解,我們可以從另一個方向去理解一下它。
你這些人類我是一名程序員,也是一個人,我可能:
有一個響亮亮的名稱
在某一天出生
是個男人
我能行走
我還能跑步
還能跳躍
能說話
我還能寫代碼
那么,在 JavaScript 中,我們可能像下面這樣表達我:
const me = { name: "大胡子農同工潘半仙", birth: "1988-08-08", sex: "male", walk: function (speed, direction, duration) { // 以 speed 的速度向 direction 方向行走 duration 長的時間 }, run: function (speed, direction, duration) { // 像跑步一樣,速度 }, jump: function (high, direction, angle) { // 以 angle 角度向 direction 方向跳 high 高 }, speak: function (letters) { // 說出 letters 這些詞 }, coding: function (language, line) { // 寫程序呢 } }你們這些人類
當然,這個世界上不可能只有我一個程序員,更不可能只有我一個人,就像我們這個小公司,就有七八百人,似乎所有這些人的數據都保存在數據庫里面:
name | sex | birth |
---|---|---|
潘韜 | male | 1988-08-08 |
高超 | male | 1985-08-09 |
春雨 | male | 1999-08-08 |
我們從數據庫中查詢出上面這幾條記錄,在 JavaScript 可能表示為一個二維數據,然后要創(chuàng)建出這三個人來,可能是下面這樣的:
const people = DB.query() // people = [["潘韜", "male", "1988-08-08"], [...], [...]] for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} } }重復的資源占用
上面大家已經發(fā)現,像上面這樣去創(chuàng)建三個對象, walk、run、jump、speak、coding 這五件能做的事情(方法),其實做法都一樣,但是我們卻重復的去描述該怎么做了,其實就占用了很多資源,所以,我們可能會像下面這樣改進一下:
const walk = function walk () {} const run = function run () {} const jump = function jump () {} const speak = function speak () {} const coding = function coding () {} for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, walk, run, jump, speak, coding } }
不同的人共用相同的資源(方法)
但是這個世界不止有人類對,人類相比于這個世界上的其它生物來講,數量根本就值得一提,如果像上面這樣,可能各種不同物種能做的事情都會要定義出不同的函數,蠕動肯定不是人類會去做的事情,但很多別的生物會做,那么為了代碼管理方便,我們把人能做的所有事情都放在一個對象里面,這樣就相當于有了一個命名空間了,不會再跟別的物種相沖突:
const whatPeopleCanDo = { walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} } for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, ...whatPeopleCanDo } }原型
但是,有的人可能我們并不知道他的 sex 信息是多少,有的也有可能不知道 birth 是多少,但是我們希望在創(chuàng)建這個人的時候,能給不知道的數據一些初始數據,所以, whatPeopleCanDo 并不能完全的表達出一個人,我們再改進:
const peopleLike = { name: "", sex: "unknown", birth: "", walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} } for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { ...peopleLike, name: name || peopleLike.name, sex: sex || peopleLike.sex, birth: birth || peopleLike.birth } }
這樣一來,我們就可以為不知道的屬性加一些默認值,我們稱 peopleLike 這個東東就為原型,它表示了像人類這樣的物種有哪些屬性,能干什么事情。
原型鏈雖然上面已經比最開始的版本好得多了,但是還是能有很大的改進空間,我們現在像下面這樣改一下:
const peoplePrototype = { name: "", sex: "unknown", birth: "", walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} } for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name: name || peoplePrototype.name, sex: sex || peoplePrototype.sex, birth: birth || peoplePrototype.birth, __proto__: peoplePrototype } }
我們不再把人類原型里面的所有方法都綁定到某個人身上,而是像上面這樣,用一個特殊的字段 __proto__ 來指定:我的原型是 peoplePrototype 這個對象,同時,我們還制定了一個規(guī)則:如果你想請求我的某個方法,在我自己身上沒有,那就去我的原型上面找吧,如果我的原型上面沒有,那就去我的原型的原型上面去找,直到某個位置,沒有更上層的原型為止
像上面這樣創(chuàng)建的 people 對象,有自己的屬性,但是當我們去訪問 people.speak() 方法的時候,其實訪問的是 people.__proto__.speak(),這是我們的規(guī)則。
更優(yōu)雅的創(chuàng)建新新人類我們總不能在需要創(chuàng)建新人的時候,都像上面這樣,自己去寫一個對象,然后再手工指定它的原型是什么,所以,我們可以創(chuàng)建一個函數,專門用來生成人類的:
const peoplePrototype = { name: "", sex: "unknown", birth: "", walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} } const makePeople = function makePeople(name, sex, birth) { let people = {} people.name = name || peoplePrototype.name people.sex = sex || peoplePrototype.sex people.birth = birth || peoplePrototype.birth people.__proto__ = peoplePrototype return people } people = people.map(makePeople)
現在這樣我們只需要引入 makePeople 這個函數就可以隨時隨地創(chuàng)建新人了。
更優(yōu)雅一點的改進顯然,上面這樣并不是最好的辦法,定義了一個原型,又定義了一個原型對象,我們可以把這兩個合并到一起,所以,就可以有下面這樣的實現了:
const People = function People (name, sex, birth) { let people = {} people.name = name || People.prototype.name people.sex = sex || People.prototype.sex people.birth = birth || People.prototype.birth people.__proto__ = People.prototype return people } People.prototype = { name: "", sex: "unknown", birth: "", walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} }
我們直接把創(chuàng)建人類的那個函數叫作 People,這個函數有一個屬性叫 prototype,它表示用我這個函數創(chuàng)建的對象的原型是什么,這個函數做的事情還是以前那些事兒,創(chuàng)建臨時對象,設置對象的屬性,綁定一下原型,然后返回。
神奇的 this我們除了人,還有別的動物,比如 Tiger、Fish等,按上面的方式,在 Tiger() 或者 Fish() 函數里面都會建立不同的 tiger 或者 fish 名稱的臨時對象,這樣太麻煩,我們把這種函數創(chuàng)建出來的對象,都可以統(tǒng)一叫作“這個對象” ,也就是 this object,不在關心是人是鬼,統(tǒng)一把所有的臨時對象都叫 thisObject 或者更簡單的就叫作:這個,即 this。
const People = function People (name, sex, birth) { let this = {} this.name = name || People.prototype.name this.sex = sex || People.prototype.sex this.birth = birth || People.prototype.birth this.__proto__ = People.prototype return this }
當然,上面的這一段代碼是有問題的,只是假想一樣,這樣是不是可行。
new到現在為止,我們發(fā)現了整個代碼的演變,是時候引出這個 new 了,它來干什么呢?它后面接一個類似上面這種 People 的函數,表示我需要創(chuàng)建一個 People 的實例,它的發(fā)明就是為了解決上面這些所有重復的事情,有了 new 之后,我們不需要再每一次定義一個臨時對象,在 new 的上下文關系中,會在 People 函數體內自動為創(chuàng)建一個臨時變量 this,這個就表示即將被創(chuàng)建出來的對象。同時,對于使用 new 創(chuàng)建的實例,會自動的綁定到創(chuàng)建函數的 prototype 作為原型,還會自動為 People 創(chuàng)建一個 constructor 函數,表示這個原型的創(chuàng)建函數是什么,所以,我們可以改成下面這樣的了:
const People = function People (name, sex, birth) { this.name = name || People.prototype.name this.sex = sex || People.prototype.sex this.birth = birth || People.prototype.birth } People.prototype.name = "" People.prototype.sex = "unknown" People.prototype.birth = "" People.prototype.walk = function () {} People.prototype.run = function () {} People.prototype.jump = function () {} People.prototype.speak = function () {} People.prototype.coding = function () {} people = people.map(p => new People(...p))總結
new 到底干了什么?當 new People() 的時候
創(chuàng)建臨時變量 this,并將 this 綁定到 People 函數體里
執(zhí)行 People.prototype.constructor = People
執(zhí)行 this.__proto__ = People.prototype
執(zhí)行 People 函數體中的自定義
返回新創(chuàng)建的對象
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/103654.html
摘要:北山愚公者年且九十面山而居。工廠模式愚小公北山愚小小公北山工廠模式比較明顯的一個缺點就是由于生成并返回了一個中間對象,所以不能判斷對象的類型。 ??太行、王屋二山,方七百里,高萬仞。本在冀州之南,河陽之北....... ??嗯,按照慣例,第一句話就是騙你們點進來的。在讀本文之前,希望你對Javascript的原型和原型鏈有一定了解,這有助于你更好的理解本文,之前有寫過一篇相關文章,點此...
摘要:我們用一張圖表示構造函數和實例原型之間的關系好了構造函數和實例原型之間的關系我們已經梳理清楚了,那我們怎么表示實例與實例原型,也就是或者和之間的關系呢。 開篇: 在Brendan Eich大神為JavaScript設計面向對象系統(tǒng)的時候,借鑒了Self 和Smalltalk這兩門基于原型的語言,之所以選擇基于原型的面向對象系統(tǒng),并不是因為時間匆忙,它設計起來相對簡單,而是因為從一開始B...
摘要:我們用一張圖表示構造函數和實例原型之間的關系好了構造函數和實例原型之間的關系我們已經梳理清楚了,那我們怎么表示實例與實例原型,也就是或者和之間的關系呢。 開篇: 在Brendan Eich大神為JavaScript設計面向對象系統(tǒng)的時候,借鑒了Self 和Smalltalk這兩門基于原型的語言,之所以選擇基于原型的面向對象系統(tǒng),并不是因為時間匆忙,它設計起來相對簡單,而是因為從一開始B...
摘要:我們通過一個簡單的例子與圖示,來了解構造函數,實例與原型三者之間的關系。而原型對象的指向構造函數。于是根據構造函數與原型的特性,我們就可以將在構造函數中,通過聲明的屬性與方法稱為私有變量與方法,它們被當前被某一個實例對象所獨有。 showImg(https://segmentfault.com/img/remote/1460000008593382); 如果要我總結一下學習前端以來我遇...
摘要:創(chuàng)建一個新的對象即實例對象把新對象的指向后面構造函數的原型對象。簡單來驗證一下等同與對象沒有原型對象的原型對像等同于構造函數是等同于,構造函數是七原型鏈的作用其實,原型鏈的根本作用就是為了屬性的讀取。 首先說一下,函數創(chuàng)建的相關知識 在JavaScript中,我們創(chuàng)建一個函數A(就是聲明一個函數), 那么 js引擎 就會用構造函數Function來創(chuàng)建這個函數。所以,所有的函數的con...
閱讀 2322·2023-04-26 00:01
閱讀 809·2021-10-27 14:13
閱讀 1840·2021-09-02 15:11
閱讀 3393·2019-08-29 12:52
閱讀 542·2019-08-26 12:00
閱讀 2575·2019-08-26 10:57
閱讀 3417·2019-08-26 10:32
閱讀 2859·2019-08-23 18:29