JavaScript的詳細(xì)分析示例

小編給大家分享一下JavaScript的詳細(xì)分析示例,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

創(chuàng)新互聯(lián)建站是一家集網(wǎng)站建設(shè),黃平企業(yè)網(wǎng)站建設(shè),黃平品牌網(wǎng)站建設(shè),網(wǎng)站定制,黃平網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,黃平網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

一、操作符new

在講解它之前我們首先需要澄清一個(gè)非常常見(jiàn)的關(guān)于 JavaScript 中函數(shù)和對(duì)象的誤解:

在傳統(tǒng)的面向類的語(yǔ)言中,“構(gòu)造函數(shù)”是類中的一些特殊方法,使用 new 初始化類時(shí)會(huì)調(diào)用類中的構(gòu)造函數(shù)。通常的形式是這樣的:

something = new MyClass(..);

JavaScript 也有一個(gè) new 操作符,使用方法看起來(lái)也和那些面向類的語(yǔ)言一樣,絕大多數(shù)開(kāi)發(fā)者都認(rèn)為 JavaScript 中 new 的機(jī)制也和那些語(yǔ)言一樣。然而,JavaScript 中 new 的機(jī)制實(shí)際上和面向類的語(yǔ)言完全不同。

首先我們重新定義一下 JavaScript 中的“構(gòu)造函數(shù)”。在 JavaScript 中,構(gòu)造函數(shù)只是一些使用 new 操作符時(shí)被調(diào)用的函數(shù)。它們并不會(huì)屬于某個(gè)類,也不會(huì)實(shí)例化一個(gè)類。實(shí)際上,它們甚至都不能說(shuō)是一種特殊的函數(shù)類型,它們只是被 new 操作符調(diào)用的普通函數(shù)而已。

實(shí)際上并不存在所謂的“構(gòu)造函數(shù)”,只有對(duì)于函數(shù)的“構(gòu)造調(diào)用”。 使用 new 來(lái)調(diào)用函數(shù),或者說(shuō)發(fā)生構(gòu)造函數(shù)調(diào)用時(shí),會(huì)自動(dòng)執(zhí)行下面的操作:

  1. 創(chuàng)建(或者說(shuō)構(gòu)造)一個(gè)全新的對(duì)象;
  2. 這個(gè)新對(duì)象會(huì)被執(zhí)行 [[ 原型 ]] 連接;
  3. 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this ;
  4. 如果函數(shù)沒(méi)有返回其他對(duì)象,那么 new 表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。

因此,如果我們要想寫出一個(gè)合乎理論的 new ,就必須嚴(yán)格按照上面的步驟,落實(shí)到代碼上就是:

/**
* @param {fn} Function(any) 構(gòu)造函數(shù)
* @param {arg1, arg2, ...} 指定的參數(shù)列表
*/
function myNew (fn, ...args) {
    // 創(chuàng)建一個(gè)新對(duì)象,并把它的原型鏈(__proto__)指向構(gòu)造函數(shù)的原型對(duì)象
    const instance = Object.create(fn.prototype)

    // 把新對(duì)象作為thisArgs和參數(shù)列表一起使用call或apply調(diào)用構(gòu)造函數(shù)
    const result = fn.apply(instance, args)

    如果構(gòu)造函數(shù)的執(zhí)行結(jié)果返回了對(duì)象類型的數(shù)據(jù)(排除null),則返回該對(duì)象,否則返新對(duì)象
    return (result && typeof instance === 'object') ? result : instance
}

示例代碼中,我們使用Object.create(fn.prototype)創(chuàng)建空對(duì)象,使其的原型鏈__proto__指向構(gòu)造函數(shù)的原型對(duì)象fn.prototype,后面我們也會(huì)自己手寫一個(gè)Object.create()方法搞清楚它是如何做到的。

二、操作符instanceof

在相當(dāng)長(zhǎng)的一段時(shí)間里,JavaScript 只有一些近似類的語(yǔ)法元素,如newinstanceof,不過(guò)在后來(lái)的 ES6 中新增了一些元素,比如 class 關(guān)鍵字。

在不考慮class的前提下,newinstanceof之間的關(guān)系“曖昧不清”。之所以會(huì)出現(xiàn)newinstanceof這些操作符,其主要目的就是為了向“面向?qū)ο缶幊獭笨繑n。

因此,我們既然搞懂了new,就沒(méi)有理由不去搞清楚instanceof。引用MDN上對(duì)于instanceof的描述:“instanceof 運(yùn)算符用于檢測(cè)構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上”。

看到這里,基本上明白了,instanceof的實(shí)現(xiàn)需要考驗(yàn)?zāi)銓?duì)原型鏈和prototype的理解。在JavaScript中關(guān)于原型和原型鏈的內(nèi)容需要大篇幅的內(nèi)容才能講述得清楚,而網(wǎng)上也有一些不錯(cuò)的總結(jié)博文,其中幫你徹底搞懂JS中的prototype、__proto__與constructor(圖解)就是一篇難得的精品文章,通透得梳理并總結(jié)了它們之間的關(guān)系和聯(lián)系。

《你不知道的JavaScript上卷》第二部分-第5章則更基礎(chǔ)、更全面地得介紹了原型相關(guān)的內(nèi)容,值得一讀。

以下instanceof代碼的實(shí)現(xiàn),雖然很簡(jiǎn)單,但是需要建立在你對(duì)原型和原型鏈有所了解的基礎(chǔ)之上,建議你先把以上的博文或文章看懂了再繼續(xù)。

/**
* @param {left} Object 實(shí)例對(duì)象
* @param {right} Function 構(gòu)造函數(shù)
*/
function myInstanceof (left, right) {
    // 保證運(yùn)算符右側(cè)是一個(gè)構(gòu)造函數(shù)
    if (typeof right !== 'function') {
        throw new Error('運(yùn)算符右側(cè)必須是一個(gè)構(gòu)造函數(shù)')
        return
    }

    // 如果運(yùn)算符左側(cè)是一個(gè)null或者基本數(shù)據(jù)類型的值,直接返回false 
    if (left === null || !['function', 'object'].includes(typeof left)) {
        return false
    }

    // 只要該構(gòu)造函數(shù)的原型對(duì)象出現(xiàn)在實(shí)例對(duì)象的原型鏈上,則返回true,否則返回false
    let proto = Object.getPrototypeOf(left)
    while (true) {

        // 遍歷完了目標(biāo)對(duì)象的原型鏈都沒(méi)找到那就是沒(méi)有,即到了Object.prototype

        if (proto === null) return false

        // 找到了
        if (proto === right.prototype) return true

        // 沿著原型鏈繼續(xù)向上找
        proto = Object.getPrototypeOf(proto)
    }
}

三、 Object.create()

Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__。

在《你不知道的JavaScript》中,多次用到了Object.create()這個(gè)方法去模仿傳統(tǒng)面向?qū)ο缶幊讨械摹袄^承”,其中也包括上面講到了new操作符的實(shí)現(xiàn)過(guò)程。在MDN中對(duì)它的介紹也很簡(jiǎn)短,主要內(nèi)容大都在描述可選參數(shù)propertiesObject的用法。

簡(jiǎn)單起見(jiàn),為了和new、instanceof的知識(shí)串聯(lián)起來(lái),我們只著重關(guān)注Object.create()的第一個(gè)參數(shù)proto,咱不討論propertiesObject的實(shí)現(xiàn)和具體特性。

/**
* 基礎(chǔ)版本
* @param {Object} proto
*  
*/
Object.prototype.create = function (proto) {  
    // 利用new操作符的特性:創(chuàng)建一個(gè)對(duì)象,其原型鏈(__proto__)指向構(gòu)造函數(shù)的原型對(duì)象
    function F () {}
    F.prototype = proto
    return new F()
}

/**
* 改良版本
* @param {Object} proto
*
*/
Object.prototype.createX = function (proto) {
    const obj = {}
    // 一步到位,把一個(gè)空對(duì)象的原型鏈(__proto__)指向指定原型對(duì)象即可
    Object.setPrototypeOf(obj, proto)
    return obj
}

我們可以看到,Object.create(proto)做的事情轉(zhuǎn)換成其他方法實(shí)現(xiàn)很簡(jiǎn)單,就是創(chuàng)建一個(gè)空對(duì)象,再把這個(gè)對(duì)象的原型鏈屬性(Object.setPrototype(obj, proto))指向指定的原型對(duì)象proto就可以了(不要采用直接賦值__proto__屬性的方式,因?yàn)槊總€(gè)瀏覽器的實(shí)現(xiàn)不盡相同,而且在規(guī)范中也沒(méi)有明確該屬性名)。

四、Function的原型方法:call、apply和bind

作為最經(jīng)典的手寫“勞?!眰?,callapplybind已經(jīng)被手寫了無(wú)數(shù)遍。也許本文中手寫的版本是無(wú)數(shù)個(gè)前輩們寫過(guò)的某個(gè)版本,但是有一點(diǎn)不同的是,本文會(huì)告訴你為什么要這樣寫,讓你搞懂了再寫。

在《你不知道的JavaScript上卷》第二部分的第1章和第2章,用了2章斤30頁(yè)的篇幅中詳細(xì)地介紹了this的內(nèi)容,已經(jīng)充分說(shuō)明了this的重要性和應(yīng)用場(chǎng)景的復(fù)雜性。

而我們要實(shí)現(xiàn)的call、applybind最為人所知的功能就是使用指定的thisArg去調(diào)用函數(shù),使得函數(shù)可以使用我們指定的thisArg作為它運(yùn)行時(shí)的上下文。

《你不知道的JavaScript》總結(jié)了四條規(guī)則來(lái)判斷一個(gè)運(yùn)行中函數(shù)的this到底是綁定到哪里:

  1. new 調(diào)用?綁定到新創(chuàng)建的對(duì)象。
  2. call 或者 apply (或者 bind )調(diào)用?綁定到指定的對(duì)象。
  3. 由上下文對(duì)象調(diào)用?綁定到那個(gè)上下文對(duì)象。
  4. 默認(rèn):在嚴(yán)格模式下綁定到 undefined ,否則綁定到全局對(duì)象。

更具體一點(diǎn),可以描述為:

  1. 函數(shù)是否在 new 中調(diào)用( new 綁定)?如果是的話 this 綁定的是新創(chuàng)建的對(duì)象:
var bar = new foo()
  1. 函數(shù)是否通過(guò) call 、 apply (顯式綁定)或者硬綁定(bind)調(diào)用?如果是的話, this 綁定的是指定的對(duì)象:
var bar = foo.call(obj2)復(fù)制代碼
  1. 函數(shù)是否在某個(gè)上下文對(duì)象中調(diào)用(隱式綁定)?如果是的話, this 綁定的是那個(gè)上下文對(duì)象:
var bar = obj1.foo()復(fù)制代碼
  1. 如果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定到 undefined ,否則綁定到全局對(duì)象:
var bar = foo()復(fù)制代碼

就是這樣。對(duì)于正常的函數(shù)調(diào)用來(lái)說(shuō),理解了這些知識(shí)你就可以明白 this 的綁定原理了。

至此,你已經(jīng)搞明白了this的全部綁定規(guī)則,而我們要去手寫實(shí)現(xiàn)的callapplybind只是其中的一條規(guī)則(第2條),因此,我們可以在另外3條規(guī)則的基礎(chǔ)上很容易地組織代碼實(shí)現(xiàn)。

4.1 call和apply

實(shí)現(xiàn)callapply的通常做法是使用“隱式綁定”的規(guī)則,只需要綁定thisArg對(duì)象到指定的對(duì)象就好了,即:使得函數(shù)可以在指定的上下文對(duì)象中調(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 時(shí)會(huì)自動(dòng)替換為指向全局對(duì)象,原始值會(huì)被包裝
    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ì)看下來(lái),大家都那么聰明,肯定一眼就看到了它們的精髓所在:

const fn = Symbol('fn')
const func = this
context[fn] = func

在這里,我們使用Symbol('fn')作為上下文對(duì)象的鍵,對(duì)應(yīng)的值指向我們想要綁定上下文的函數(shù)this(因?yàn)槲覀兊姆椒ㄊ锹暶髟?code>Function.prototype上的),而使用Symbol(fn)作為鍵名是為了避免和上下文對(duì)象的其他鍵名沖突,從而導(dǎo)致覆蓋了原有的屬性鍵值對(duì)。

4.2 bind

在《你不知道的JavaScript》中,手動(dòng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單版本的bind函數(shù),它稱之為“硬綁定”:

function bind(fn, obj) {
    return function() {
        return fn.apply( obj, arguments );
    };
}

硬綁定的典型應(yīng)用場(chǎng)景就是創(chuàng)建一個(gè)包裹函數(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(..) 會(huì)返回一個(gè)硬編碼的新函數(shù),它會(huì)把參數(shù)設(shè)置為 this 的上下文并調(diào)用原始函數(shù)。

MDN是這樣描述bind方法的:bind() 方法創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。

因此,我們可以在此基礎(chǔ)上實(shí)現(xiàn)我們的bind方法:

/**
* @param {context} Object 指定為 null 或 undefined 時(shí)會(huì)自動(dòng)替換為指向全局對(duì)象,原始值會(huì)被包裝
*
* @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)的原型對(duì)象覆蓋自身的原型對(duì)象,然后使用new操作符構(gòu)造出一個(gè)實(shí)例對(duì)象作為fBound的原型對(duì)象,從而實(shí)現(xiàn)繼承包裝函數(shù)的原型對(duì)象
    const fNOP = function () {}

    const fBound = function (...args2) {

        // fNOP.prototype.isPrototypeOf(this) 為true說(shuō)明當(dāng)前結(jié)果是被使用new操作符調(diào)用的,則忽略context
        return _self.apply(fNOP.prototype.isPrototypeOf(this) && context ? this : context, [...args, ...args2])
    }

    // 綁定原型對(duì)象
    fNOP.prototype = this.prototype
    fBound.prototype = new fNOP()
    return fBound
}

具體的實(shí)現(xiàn)細(xì)節(jié)都標(biāo)注了對(duì)應(yīng)的注釋,涉及到的原理都有在上面的內(nèi)容中講到,也算是一個(gè)總結(jié)和回顧吧。

五、函數(shù)柯里化

維基百科:柯里化,英語(yǔ):Currying,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù) 。

看這個(gè)解釋有一點(diǎn)抽象,我們就拿被做了無(wú)數(shù)次示例的add函數(shù),來(lái)做一個(gè)簡(jiǎn)單的實(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ù)的xy兩個(gè)參數(shù)變成了先用一個(gè)函數(shù)接收x然后返回一個(gè)函數(shù)去處理y參數(shù)?,F(xiàn)在思路應(yīng)該就比較清晰了,就是只傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。

函數(shù)柯里化在一定場(chǎng)景下,有很多好處,如:參數(shù)復(fù)用、提前確認(rèn)和延遲運(yùn)行等,具體內(nèi)容可以拜讀下這篇文章,個(gè)人覺(jué)得受益匪淺。

最簡(jiǎn)單的實(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)的話,會(huì)比較麻煩些,但是核心思想是不變的,就是在傳遞的參數(shù)滿足調(diào)用函數(shù)之前始終返回一個(gè)需要傳參剩余參數(shù)的函數(shù):

// 函數(shù)柯里化指的是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。
function curry(fn, args) {
    args = args || []

    // 獲取函數(shù)需要的參數(shù)長(zhǎng)度
    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ù)的長(zhǎng)度是否已經(jīng)滿足函數(shù)所需參數(shù)的長(zhǎng)度
        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)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

網(wǎng)站標(biāo)題:JavaScript的詳細(xì)分析示例
網(wǎng)址分享:http://muchs.cn/article12/johhdc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁(yè)設(shè)計(jì)公司服務(wù)器托管、網(wǎng)站制作、App開(kāi)發(fā)、虛擬主機(jī)做網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司