小編給大家分享一下JavaScript的詳細(xì)分析示例,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)建站是一家集網(wǎng)站建設(shè),黃平企業(yè)網(wǎng)站建設(shè),黃平品牌網(wǎng)站建設(shè),網(wǎng)站定制,黃平網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,黃平網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
在講解它之前我們首先需要澄清一個非常常見的關(guān)于 JavaScript 中函數(shù)和對象的誤解:
在傳統(tǒng)的面向類的語言中,“構(gòu)造函數(shù)”是類中的一些特殊方法,使用 new
初始化類時會調(diào)用類中的構(gòu)造函數(shù)。通常的形式是這樣的:
something = new MyClass(..);
JavaScript 也有一個 new
操作符,使用方法看起來也和那些面向類的語言一樣,絕大多數(shù)開發(fā)者都認(rèn)為 JavaScript 中 new
的機(jī)制也和那些語言一樣。然而,JavaScript 中 new
的機(jī)制實(shí)際上和面向類的語言完全不同。
首先我們重新定義一下 JavaScript 中的“構(gòu)造函數(shù)”。在 JavaScript 中,構(gòu)造函數(shù)只是一些使用 new
操作符時被調(diào)用的函數(shù)。它們并不會屬于某個類,也不會實(shí)例化一個類。實(shí)際上,它們甚至都不能說是一種特殊的函數(shù)類型,它們只是被 new
操作符調(diào)用的普通函數(shù)而已。
實(shí)際上并不存在所謂的“構(gòu)造函數(shù)”,只有對于函數(shù)的“構(gòu)造調(diào)用”。
使用 new
來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時,會自動執(zhí)行下面的操作:
因此,如果我們要想寫出一個合乎理論的 new
,就必須嚴(yán)格按照上面的步驟,落實(shí)到代碼上就是:
/** * @param {fn} Function(any) 構(gòu)造函數(shù) * @param {arg1, arg2, ...} 指定的參數(shù)列表 */ function myNew (fn, ...args) { // 創(chuàng)建一個新對象,并把它的原型鏈(__proto__)指向構(gòu)造函數(shù)的原型對象 const instance = Object.create(fn.prototype) // 把新對象作為thisArgs和參數(shù)列表一起使用call或apply調(diào)用構(gòu)造函數(shù) const result = fn.apply(instance, args) 如果構(gòu)造函數(shù)的執(zhí)行結(jié)果返回了對象類型的數(shù)據(jù)(排除null),則返回該對象,否則返新對象 return (result && typeof instance === 'object') ? result : instance }
示例代碼中,我們使用
Object.create(fn.prototype)
創(chuàng)建空對象,使其的原型鏈__proto__
指向構(gòu)造函數(shù)的原型對象fn.prototype
,后面我們也會自己手寫一個Object.create()
方法搞清楚它是如何做到的。
在相當(dāng)長的一段時間里,JavaScript 只有一些近似類的語法元素,如new
和 instanceof
,不過在后來的 ES6 中新增了一些元素,比如 class
關(guān)鍵字。
在不考慮class
的前提下,new
和instanceof
之間的關(guān)系“曖昧不清”。之所以會出現(xiàn)new
和instanceof
這些操作符,其主要目的就是為了向“面向?qū)ο缶幊獭笨繑n。
因此,我們既然搞懂了new
,就沒有理由不去搞清楚instanceof
。引用MDN上對于instanceof
的描述:“instanceof
運(yùn)算符用于檢測構(gòu)造函數(shù)的 prototype
屬性是否出現(xiàn)在某個實(shí)例對象的原型鏈上”。
看到這里,基本上明白了,instanceof
的實(shí)現(xiàn)需要考驗(yàn)?zāi)銓υ玩満?code>prototype的理解。在JavaScript中關(guān)于原型和原型鏈的內(nèi)容需要大篇幅的內(nèi)容才能講述得清楚,而網(wǎng)上也有一些不錯的總結(jié)博文,其中幫你徹底搞懂JS中的prototype、__proto__與constructor(圖解)就是一篇難得的精品文章,通透得梳理并總結(jié)了它們之間的關(guān)系和聯(lián)系。
《你不知道的JavaScript上卷》第二部分-第5章則更基礎(chǔ)、更全面地得介紹了原型相關(guān)的內(nèi)容,值得一讀。
以下instanceof
代碼的實(shí)現(xiàn),雖然很簡單,但是需要建立在你對原型和原型鏈有所了解的基礎(chǔ)之上,建議你先把以上的博文或文章看懂了再繼續(xù)。
/** * @param {left} Object 實(shí)例對象 * @param {right} Function 構(gòu)造函數(shù) */ function myInstanceof (left, right) { // 保證運(yùn)算符右側(cè)是一個構(gòu)造函數(shù) if (typeof right !== 'function') { throw new Error('運(yùn)算符右側(cè)必須是一個構(gòu)造函數(shù)') return } // 如果運(yùn)算符左側(cè)是一個null或者基本數(shù)據(jù)類型的值,直接返回false if (left === null || !['function', 'object'].includes(typeof left)) { return false } // 只要該構(gòu)造函數(shù)的原型對象出現(xiàn)在實(shí)例對象的原型鏈上,則返回true,否則返回false let proto = Object.getPrototypeOf(left) while (true) { // 遍歷完了目標(biāo)對象的原型鏈都沒找到那就是沒有,即到了Object.prototype if (proto === null) return false // 找到了 if (proto === right.prototype) return true // 沿著原型鏈繼續(xù)向上找 proto = Object.getPrototypeOf(proto) } }
Object.create()
方法創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的__proto__
。
在《你不知道的JavaScript》中,多次用到了Object.create()
這個方法去模仿傳統(tǒng)面向?qū)ο缶幊讨械摹袄^承”,其中也包括上面講到了new
操作符的實(shí)現(xiàn)過程。在MDN中對它的介紹也很簡短,主要內(nèi)容大都在描述可選參數(shù)propertiesObject
的用法。
簡單起見,為了和new
、instanceof
的知識串聯(lián)起來,我們只著重關(guān)注Object.create()
的第一個參數(shù)proto
,咱不討論propertiesObject
的實(shí)現(xiàn)和具體特性。
/** * 基礎(chǔ)版本 * @param {Object} proto * */ Object.prototype.create = function (proto) { // 利用new操作符的特性:創(chuàng)建一個對象,其原型鏈(__proto__)指向構(gòu)造函數(shù)的原型對象 function F () {} F.prototype = proto return new F() } /** * 改良版本 * @param {Object} proto * */ Object.prototype.createX = function (proto) { const obj = {} // 一步到位,把一個空對象的原型鏈(__proto__)指向指定原型對象即可 Object.setPrototypeOf(obj, proto) return obj }
我們可以看到,Object.create(proto)
做的事情轉(zhuǎn)換成其他方法實(shí)現(xiàn)很簡單,就是創(chuàng)建一個空對象,再把這個對象的原型鏈屬性(Object.setPrototype(obj, proto)
)指向指定的原型對象proto
就可以了(不要采用直接賦值__proto__
屬性的方式,因?yàn)槊總€瀏覽器的實(shí)現(xiàn)不盡相同,而且在規(guī)范中也沒有明確該屬性名)。
作為最經(jīng)典的手寫“勞?!眰儯?code>call、apply
和bind
已經(jīng)被手寫了無數(shù)遍。也許本文中手寫的版本是無數(shù)個前輩們寫過的某個版本,但是有一點(diǎn)不同的是,本文會告訴你為什么要這樣寫,讓你搞懂了再寫。
在《你不知道的JavaScript上卷》第二部分的第1章和第2章,用了2章斤30頁的篇幅中詳細(xì)地介紹了this
的內(nèi)容,已經(jīng)充分說明了this
的重要性和應(yīng)用場景的復(fù)雜性。
而我們要實(shí)現(xiàn)的call
、apply
和bind
最為人所知的功能就是使用指定的thisArg
去調(diào)用函數(shù),使得函數(shù)可以使用我們指定的thisArg
作為它運(yùn)行時的上下文。
《你不知道的JavaScript》總結(jié)了四條規(guī)則來判斷一個運(yùn)行中函數(shù)的this
到底是綁定到哪里:
new
調(diào)用?綁定到新創(chuàng)建的對象。call
或者 apply
(或者 bind
)調(diào)用?綁定到指定的對象。undefined
,否則綁定到全局對象。更具體一點(diǎn),可以描述為:
new
中調(diào)用( new
綁定)?如果是的話 this
綁定的是新創(chuàng)建的對象:var bar = new foo()
call
、 apply
(顯式綁定)或者硬綁定(bind
)調(diào)用?如果是的話, this
綁定的是指定的對象:var bar = foo.call(obj2)復(fù)制代碼
this
綁定的是那個上下文對象:var bar = obj1.foo()復(fù)制代碼
undefined
,否則綁定到全局對象:var bar = foo()復(fù)制代碼
就是這樣。對于正常的函數(shù)調(diào)用來說,理解了這些知識你就可以明白 this
的綁定原理了。
至此,你已經(jīng)搞明白了this
的全部綁定規(guī)則,而我們要去手寫實(shí)現(xiàn)的call
、apply
和bind
只是其中的一條規(guī)則(第2條),因此,我們可以在另外3條規(guī)則的基礎(chǔ)上很容易地組織代碼實(shí)現(xiàn)。
實(shí)現(xiàn)call
和apply
的通常做法是使用“隱式綁定”的規(guī)則,只需要綁定thisArg
對象到指定的對象就好了,即:使得函數(shù)可以在指定的上下文對象中調(diào)用:
const context = { name: 'ZhangSan' } function sayName () { console.log(this.name) } context.sayName = sayName context.sayName() // ZhangSan
這樣,我們就完成了“隱式綁定”。落實(shí)到具體的代碼實(shí)現(xiàn)上:
/** * @param {context} Object * @param {arg1, arg2, ...} 指定的參數(shù)列表 */ Function.prototype.call = function (context, ...args) { // 指定為 null 或 undefined 時會自動替換為指向全局對象,原始值會被包裝 if (context === null || context === undefined) { context = window } else if (typeof context !== 'object') { context = new context.constructor(context) } else { context = context } const func = this const fn = Symbol('fn') context[fn] = func const result = context[fn](...args) delete context[fn] return result } /** * @param {context} * @param {args} Array 參數(shù)數(shù)組 */ Function.prototype.apply = function (context, args) { // 和call一樣的原理 if (context === null || context === undefined) { context = window } else if (typeof context !== 'object') { context = new context.constructor(context) } else { context = context } const fn = Symbol('fn') const func = this context[fn] = func const result = context[fn](...args) delete context[fn] return result }
細(xì)看下來,大家都那么聰明,肯定一眼就看到了它們的精髓所在:
const fn = Symbol('fn') const func = this context[fn] = func
在這里,我們使用Symbol('fn')
作為上下文對象的鍵,對應(yīng)的值指向我們想要綁定上下文的函數(shù)this
(因?yàn)槲覀兊姆椒ㄊ锹暶髟?code>Function.prototype上的),而使用Symbol(fn)
作為鍵名是為了避免和上下文對象的其他鍵名沖突,從而導(dǎo)致覆蓋了原有的屬性鍵值對。
在《你不知道的JavaScript》中,手動實(shí)現(xiàn)了一個簡單版本的bind
函數(shù),它稱之為“硬綁定”:
function bind(fn, obj) { return function() { return fn.apply( obj, arguments ); }; }
硬綁定的典型應(yīng)用場景就是創(chuàng)建一個包裹函數(shù),傳入所有的參數(shù)并返回接收到的所有值。
由于硬綁定是一種非常常用的模式,所以在 ES5 中提供了內(nèi)置的方法 Function.prototype.bind
,它的用法如下:
function foo(something) { console.log( this.a, something ) return this.a + something; } var obj = { a:2 } var bar = foo.bind( obj ) var b = bar( 3 ); // 2 3 console.log( b ); // 5
bind(..)
會返回一個硬編碼的新函數(shù),它會把參數(shù)設(shè)置為 this
的上下文并調(diào)用原始函數(shù)。
MDN是這樣描述
bind
方法的:bind()
方法創(chuàng)建一個新的函數(shù),在bind()
被調(diào)用時,這個新函數(shù)的this
被指定為bind()
的第一個參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時使用。
因此,我們可以在此基礎(chǔ)上實(shí)現(xiàn)我們的bind
方法:
/** * @param {context} Object 指定為 null 或 undefined 時會自動替換為指向全局對象,原始值會被包裝 * * @param {arg1, arg2, ...} 指定的參數(shù)列表 * * 如果 bind 函數(shù)的參數(shù)列表為空,或者thisArg是null或undefined,執(zhí)行作用域的 this 將被視為新函數(shù)的 thisArg */ Function.prototype.bind = function (context, ...args) { if (typeof this !== 'function') { throw new TypeError('必須使用函數(shù)調(diào)用此方法'); } const _self = this // fNOP存在的意義: // 1. 判斷返回的fBound是否被new調(diào)用了,如果是被new調(diào)用了,那么fNOP.prototype自然是fBound()中this的原型 // 2. 使用包裝函數(shù)(_self)的原型對象覆蓋自身的原型對象,然后使用new操作符構(gòu)造出一個實(shí)例對象作為fBound的原型對象,從而實(shí)現(xiàn)繼承包裝函數(shù)的原型對象 const fNOP = function () {} const fBound = function (...args2) { // fNOP.prototype.isPrototypeOf(this) 為true說明當(dāng)前結(jié)果是被使用new操作符調(diào)用的,則忽略context return _self.apply(fNOP.prototype.isPrototypeOf(this) && context ? this : context, [...args, ...args2]) } // 綁定原型對象 fNOP.prototype = this.prototype fBound.prototype = new fNOP() return fBound }
具體的實(shí)現(xiàn)細(xì)節(jié)都標(biāo)注了對應(yīng)的注釋,涉及到的原理都有在上面的內(nèi)容中講到,也算是一個總結(jié)和回顧吧。
維基百科:柯里化,英語:Currying,是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù) 。
看這個解釋有一點(diǎn)抽象,我們就拿被做了無數(shù)次示例的add
函數(shù),來做一個簡單的實(shí)現(xiàn):
// 普通的add函數(shù) function add(x, y) { return x + y } // Currying后 function curryingAdd(x) { return function (y) { return x + y } } add(1, 2) // 3 curryingAdd(1)(2) // 3
實(shí)際上就是把add
函數(shù)的x
,y
兩個參數(shù)變成了先用一個函數(shù)接收x
然后返回一個函數(shù)去處理y
參數(shù)。現(xiàn)在思路應(yīng)該就比較清晰了,就是只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)。
函數(shù)柯里化在一定場景下,有很多好處,如:參數(shù)復(fù)用、提前確認(rèn)和延遲運(yùn)行等,具體內(nèi)容可以拜讀下這篇文章,個人覺得受益匪淺。
最簡單的實(shí)現(xiàn)函數(shù)柯里化的方式就是使用Function.prototype.bind
,即:
function curry(fn, ...args) { return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); }
如果用ES5代碼實(shí)現(xiàn)的話,會比較麻煩些,但是核心思想是不變的,就是在傳遞的參數(shù)滿足調(diào)用函數(shù)之前始終返回一個需要傳參剩余參數(shù)的函數(shù):
// 函數(shù)柯里化指的是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)。 function curry(fn, args) { args = args || [] // 獲取函數(shù)需要的參數(shù)長度 let length = fn.length return function() { let subArgs = args.slice(0) // 拼接得到現(xiàn)有的所有參數(shù) for (let i = 0; i < arguments.length; i++) { subArgs.push(arguments[i]) } // 判斷參數(shù)的長度是否已經(jīng)滿足函數(shù)所需參數(shù)的長度 if (subArgs.length >= length) { // 如果滿足,執(zhí)行函數(shù) return fn.apply(this, subArgs) } else { // 如果不滿足,遞歸返回科里化的函數(shù),等待參數(shù)的傳入 return curry.call(this, fn, subArgs) } }; }
以上是JavaScript的詳細(xì)分析示例的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
網(wǎng)站標(biāo)題:JavaScript的詳細(xì)分析示例
網(wǎng)址分享:http://jinyejixie.com/article12/johhdc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計(jì)公司、服務(wù)器托管、網(wǎng)站制作、App開發(fā)、虛擬主機(jī)、做網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)