成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

可能遇到假的面試題:不用call和apply方法模擬實(shí)現(xiàn)ES5的bind方法

李世贊 / 3252人閱讀

摘要:來自朋友去某信用卡管家的做的一道面試題,用原生模擬的方法,不準(zhǔn)用和方法。他們的用途相同,都是在特定的作用域中調(diào)用函數(shù)。不同之處在于,方法傳遞給調(diào)用函數(shù)的參數(shù)是逐個(gè)列出的,而則是要寫在數(shù)組中。

本文首發(fā)我的個(gè)人博客:前端小密圈,評(píng)論交流送1024邀請(qǐng)碼,嘿嘿嘿?。

來自朋友去某信用卡管家的做的一道面試題,用原生JavaScript模擬ES5bind方法,不準(zhǔn)用callbind方法。

至于結(jié)果嘛。。。那個(gè)人當(dāng)然是沒寫出來,我就自己嘗試研究了一番,其實(shí)早就寫了,一直沒有組織好語言發(fā)出來。

額。。。這個(gè)題有點(diǎn)刁鉆,這是對(duì)JavaScript基本功很好的一個(gè)檢測,看你JavaScript掌握的怎么樣以及平時(shí)有沒有去深入研究一些方法的實(shí)現(xiàn),簡而言之,就是有沒有折騰精神。

不準(zhǔn)用不用callapply方法,這個(gè)沒啥好說的,不準(zhǔn)用我們就用原生JavaScript先來模擬一個(gè)apply方法,感興趣的童鞋也可以看看chromev8怎么實(shí)現(xiàn)這個(gè)方法的,這里我只按照自己的思維實(shí)現(xiàn),在模擬之前我們先要明白和了解原生callapply方法是什么。

簡單粗暴地來說,call,apply,bind是用于綁定this指向的。(如果你還不了解JS中this的指向問題,以及執(zhí)行環(huán)境上下文的奧秘,這篇文章暫時(shí)就不太適合閱讀)。

什么是call和apply方法

我們多帶帶看看ECMAScript規(guī)范對(duì)apply的定義,看個(gè)大概就行:

15.3.4.3 Function.prototype.apply (thisArg, argArray)

順便貼一貼中文版,免得翻譯一下,中文版地址:

通過定義簡單說一下call和apply方法,他們就是參數(shù)不同,作用基本相同。

1、每個(gè)函數(shù)都包含兩個(gè)非繼承而來的方法:apply()和call()。
2、他們的用途相同,都是在特定的作用域中調(diào)用函數(shù)。
3、接收參數(shù)方面不同,apply()接收兩個(gè)參數(shù),一個(gè)是函數(shù)運(yùn)行的作用域(this),另一個(gè)是參數(shù)數(shù)組。
4、call()方法第一個(gè)參數(shù)與apply()方法相同,但傳遞給函數(shù)的參數(shù)必須列舉出來。

知道定義然后,直接看個(gè)簡單的demo

var jawil = {
    name: "jawil",
    sayHello: function (age) {
         console.log("hello, i am ", this.name + " " + age + " years old");
     }
};

var  lulin = {
    name: "lulin",
};

jawil.sayHello(24);

// hello, i am jawil 24 years old

然后看看使用applycall之后的輸出:

jawil.sayHello.call(lulin, 24);// hello, i am lulin 24 years old

jawil.sayHello.apply(lulin, [24]);// hello, i am lulin 24 years old

結(jié)果都相同。從寫法上我們就能看出二者之間的異同。相同之處在于,第一個(gè)參數(shù)都是要綁定的上下文,后面的參數(shù)是要傳遞給調(diào)用該方法的函數(shù)的。不同之處在于,call方法傳遞給調(diào)用函數(shù)的參數(shù)是逐個(gè)列出的,而apply則是要寫在數(shù)組中。

總結(jié)一句話介紹callapply

call()方法在使用一個(gè)指定的this值和若干個(gè)指定的參數(shù)值的前提下調(diào)用某個(gè)函數(shù)或方法。
apply()方法在使用一個(gè)指定的this值和參數(shù)值必須是數(shù)組類型的前提下調(diào)用某個(gè)函數(shù)或方法。

分析call和apply的原理

上面代碼,我們注意到了兩點(diǎn):

callapply改變了this的指向,指向到lulin

sayHello函數(shù)執(zhí)行了

這里默認(rèn)大家都對(duì)this有一個(gè)基本的了解,知道什么時(shí)候this該指向誰,我們結(jié)合這兩句話來分析這個(gè)通用函數(shù):f.apply(o),我們直接看一本書對(duì)其中原理的解讀,具體什么書,我也不知道,參數(shù)我們先不管,先了解其中的大致原理。

正好可以打印lulin而不是之前的jawil了,哎,不容易??!?

模擬實(shí)現(xiàn)第二步

最一開始也講了,apply函數(shù)還能給定參數(shù)執(zhí)行函數(shù)。舉個(gè)例子:

var jawil = {
    name: "jawil",
    sayHello: function (age) {
         console.log(this.name,age);
     }
};

var  lulin = {
    name: "lulin",
};

jawil.sayHello.apply(lulin,[24])//lulin 24

注意:傳入的參數(shù)就是一個(gè)數(shù)組,很簡單,我們可以從Arguments對(duì)象中取值,Arguments不知道是何物,趕緊補(bǔ)習(xí),此文也不太適合初學(xué)者,第二個(gè)參數(shù)就是數(shù)組對(duì)象,但是執(zhí)行的時(shí)候要把數(shù)組數(shù)值傳遞給函數(shù)當(dāng)參數(shù),然后執(zhí)行,這就需要一點(diǎn)小技巧。

參數(shù)問題其實(shí)很簡單,我們先偷個(gè)懶,我們接著要把這個(gè)參數(shù)數(shù)組放到要執(zhí)行的函數(shù)的參數(shù)里面去。

Function.prototype.applyTwo = function(context) {
    // 首先要獲取調(diào)用call的函數(shù),用this可以獲取
    context.fn = this;
    var args = arguments[1] //獲取傳入的數(shù)組參數(shù)
    context.fn(args.join(",");
    delete context.fn;
}

很簡單是不是,那你就錯(cuò)了,數(shù)組join方法返回的是啥?

typeof [1,2,3,4].join(",")//string

Too young,too simple啊,最后是一個(gè) "1,2,3,4" 的字符串,其實(shí)就是一個(gè)參數(shù),肯定不行啦。

也許有人會(huì)想到用ES6的一些奇淫方法,不過applyES3的方法,我們?yōu)榱四M實(shí)現(xiàn)一個(gè)ES3的方法,要用到ES6的方法,反正面試官也沒說不準(zhǔn)這樣。但是我們這次用eval方法拼成一個(gè)函數(shù),類似于這樣:

eval("context.fn(" + args +")")

先簡單了解一下eval函數(shù)吧
定義和用法

eval() 函數(shù)可計(jì)算某個(gè)字符串,并執(zhí)行其中的的 JavaScript 代碼。

語法:
eval(string)

string必需。要計(jì)算的字符串,其中含有要計(jì)算的 JavaScript 表達(dá)式或要執(zhí)行的語句。該方法只接受原始字符串作為參數(shù),如果 string 參數(shù)不是原始字符串,那么該方法將不作任何改變地返回。因此請(qǐng)不要為 eval() 函數(shù)傳遞 String 對(duì)象來作為參數(shù)。

簡單來說吧,就是用JavaScript的解析引擎來解析這一堆字符串里面的內(nèi)容,這么說吧,你可以這么理解,你把eval看成是

第二版代碼大致如下:

Function.prototype.applyTwo = function(context) {
    var args = arguments[1]; //獲取傳入的數(shù)組參數(shù)
    context.fn = this; //假想context對(duì)象預(yù)先不存在名為fn的屬性
    var fnStr = "context.fn(";
    for (var i = 0; i < args.length; i++) {
        fnStr += i == args.length - 1 ? args[i] : args[i] + ",";
    }
    fnStr += ")";//得到"context.fn(arg1,arg2,arg3...)"這個(gè)字符串在,最后用eval執(zhí)行
    eval(fnStr); //還是eval強(qiáng)大
    delete context.fn; //執(zhí)行完畢之后刪除這個(gè)屬性
}
//測試一下
var jawil = {
    name: "jawil",
    sayHello: function (age) {
         console.log(this.name,age);
     }
};

var  lulin = {
    name: "lulin",
};

jawil.sayHello.applyTwo(lulin,[24])//lulin 24

好像就行了是不是,其實(shí)這只是最粗糙的版本,能用,但是不完善,完成了大約百分之六七十了。

模擬實(shí)現(xiàn)第三步

其實(shí)還有幾個(gè)小地方需要注意:

1.this參數(shù)可以傳null或者不傳,當(dāng)為null的時(shí)候,視為指向window

舉個(gè)兩個(gè)簡單栗子栗子?:
demo1:

var name = "jawil";

function sayHello() {
    console.log(this.name);
}

sayHello.apply(null); // "jawil"

demo2:

var name = "jawil";

function sayHello() {
    console.log(this.name);
}

sayHello.apply(); // "jawil"

2.函數(shù)是可以有返回值的.

舉個(gè)簡單栗子?:

var obj = {
    name: "jawil"
}

function sayHello(age) {
    return {
        name: this.name,
        age: age
    }
}

console.log(sayHello.apply(obj,[24]));// {name: "jawil", age: 24}

這些都是小問題,想到了,就很好解決。我們來看看此時(shí)的第三版apply模擬方法。

//原生JavaScript封裝apply方法,第三版
Function.prototype.applyThree = function(context) {
    var context = context || window
    var args = arguments[1] //獲取傳入的數(shù)組參數(shù)
    context.fn = this //假想context對(duì)象預(yù)先不存在名為fn的屬性
    if (args == void 0) { //沒有傳入?yún)?shù)直接執(zhí)行
        return context.fn()
    }
    var fnStr = "context.fn("
    for (var i = 0; i < args.length; i++) {
        //得到"context.fn(arg1,arg2,arg3...)"這個(gè)字符串在,最后用eval執(zhí)行
        fnStr += i == args.length - 1 ? args[i] : args[i] + ","
    }
    fnStr += ")"
    var returnValue = eval(fnStr) //還是eval強(qiáng)大
    delete context.fn //執(zhí)行完畢之后刪除這個(gè)屬性
    return returnValue
}

好緊張,再來做個(gè)小測試,demo,應(yīng)該不會(huì)出問題:

var obj = {
    name: "jawil"
}

function sayHello(age) {
    return {
        name: this.name,
        age: age
    }
}

console.log(sayHello.applyThree(obj,[24]));// 完美輸出{name: "jawil", age: 24}

完美?perfact?這就好了,不存在的,我們來看看第四步的實(shí)現(xiàn)。

模擬實(shí)現(xiàn)第四步

其實(shí)一開始就埋下了一個(gè)隱患,我們看看這段代碼:

Function.prototype.applyThree = function(context) {
    var context = context || window
    var args = arguments[1] //獲取傳入的數(shù)組參數(shù)
    context.fn = this //假想context對(duì)象預(yù)先不存在名為fn的屬性
    ......
}

就是這句話, context.fn = this //假想context對(duì)象預(yù)先不存在名為fn的屬性,這就是一開始的隱患,我們只是假設(shè),但是并不能防止contenx對(duì)象一開始就沒有這個(gè)屬性,要想做到完美,就要保證這個(gè)context.fn中的fn的唯一性。

于是我自然而然的想到了強(qiáng)大的ES6,這玩意還是好用啊,幸好早就了解并一直在使用ES6,還沒有學(xué)習(xí)過ES6的童鞋趕緊學(xué)習(xí)一下,沒有壞處的。

重新復(fù)習(xí)下新知識(shí):
基本數(shù)據(jù)類型有6種:Undefined、Null、布爾值(Boolean)字符串(String)、數(shù)值(Number)、對(duì)象(Object)

ES5對(duì)象屬性名都是字符串容易造成屬性名的沖突。
舉個(gè)栗子?:

var a = { name: "jawil"};
a.name = "lulin";
//這樣就會(huì)重寫屬性

ES6引入了一種新的原始數(shù)據(jù)類型Symbol,表示獨(dú)一無二的值。

注意,Symbol函數(shù)前不能使用new命令,否則會(huì)報(bào)錯(cuò)。這是因?yàn)樯傻?b>Symbol是一個(gè)原始類型的值,不是對(duì)象

Symbol函數(shù)可以接受一個(gè)字符串作為參數(shù),表示對(duì)Symbol實(shí)例的描述,主要是為了在控制臺(tái)顯示,或者轉(zhuǎn)為字符串時(shí),比較容易區(qū)分。

// 沒有參數(shù)的情況
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

// 有參數(shù)的情況
var s1 = Symbol("foo");
var s2 = Symbol("foo");

s1 === s2 // false

注意:Symbol值不能與其他類型的值進(jìn)行運(yùn)算。

作為屬性名的Symbol

var mySymbol = Symbol();

// 第一種寫法
var a = {};
a[mySymbol] = "Hello!";

// 第二種寫法
var a = {
  [mySymbol]: "Hello!"
};

// 第三種寫法
var a = {};
Object.defineProperty(a, mySymbol, { value: "Hello!" });

// 以上寫法都得到同樣結(jié)果
a[mySymbol] // "Hello!"

注意,Symbol值作為對(duì)象屬性名時(shí),不能用點(diǎn)運(yùn)算符。

看看下面這個(gè)栗子?:

var a = {};
var name = Symbol();
a.name = "jawil";
a[name] = "lulin";
console.log(a.name,a[name]);             //jawil,lulin

Symbol值作為屬性名時(shí),該屬性還是公開屬性,不是私有屬性。

這個(gè)有點(diǎn)類似于java中的protected屬性(protected和private的區(qū)別:在類的外部都是不可以訪問的,在類內(nèi)的子類可以繼承protected不可以繼承private)

但是這里的Symbol在類外部也是可以訪問的,只是不會(huì)出現(xiàn)在for...in、for...of循環(huán)中,也不會(huì)被Object.keys()、Object.getOwnPropertyNames()返回。但有一個(gè)Object.getOwnPropertySymbols方法,可以獲取指定對(duì)象的所有Symbol屬性名。

看看第四版的實(shí)現(xiàn)demo,想必大家了解上面知識(shí)已經(jīng)猜得到怎么寫了,很簡單。
直接加個(gè)var fn = Symbol()就行了,,,

//原生JavaScript封裝apply方法,第四版
Function.prototype.applyFour = function(context) {
    var context = context || window
    var args = arguments[1] //獲取傳入的數(shù)組參數(shù)
    var fn = Symbol()
    context[fn] = this //假想context對(duì)象預(yù)先不存在名為fn的屬性
    if (args == void 0) { //沒有傳入?yún)?shù)直接執(zhí)行
        return context[fn]()
    }
    var fnStr = "context[fn]("
    for (var i = 0; i < args.length; i++) {
        //得到"context.fn(arg1,arg2,arg3...)"這個(gè)字符串在,最后用eval執(zhí)行
        fnStr += i == args.length - 1 ? args[i] : args[i] + ","
    }
    fnStr += ")"
    var returnValue = eval(fnStr) //還是eval強(qiáng)大
    delete context[fn] //執(zhí)行完畢之后刪除這個(gè)屬性
    return returnValue
}
模擬實(shí)現(xiàn)第五步

呃呃呃額額,慢著,ES3就出現(xiàn)的方法,你用ES6來實(shí)現(xiàn),你好意思么?你可能會(huì)說,不管黑貓白貓,只要能抓住老鼠的貓就是好貓,面試官直說不準(zhǔn)用callapply方法但是沒說不準(zhǔn)用ES6語法啊。

反正公說公有理婆說婆有理,這里還是不用Symbol方法實(shí)現(xiàn)一下,我們知道,ES6其實(shí)都是語法糖,ES6能寫的,咋們ES5都能實(shí)現(xiàn),這就導(dǎo)致了babel這類把ES6語法轉(zhuǎn)化成ES5的代碼了。

至于babelSymbol屬性轉(zhuǎn)換成啥代碼了,我也沒去看,有興趣的可以看一下稍微研究一下,這里我說一下簡單的模擬。

ES5 沒有 Sybmol,屬性名稱只可能是一個(gè)字符串,如果我們能做到這個(gè)字符串不可預(yù)料,那么就基本達(dá)到目標(biāo)。要達(dá)到不可預(yù)期,一個(gè)隨機(jī)數(shù)基本上就解決了。

//簡單模擬Symbol屬性
function jawilSymbol(obj) {
    var unique_proper = "00" + Math.random();
    if (obj.hasOwnProperty(unique_proper)) {
        arguments.callee(obj)//如果obj已經(jīng)有了這個(gè)屬性,遞歸調(diào)用,直到?jīng)]有這個(gè)屬性
    } else {
        return unique_proper;
    }
}
//原生JavaScript封裝apply方法,第五版
Function.prototype.applyFive = function(context) {
    var context = context || window
    var args = arguments[1] //獲取傳入的數(shù)組參數(shù)
    var fn = jawilSymbol(context);
    context[fn] = this //假想context對(duì)象預(yù)先不存在名為fn的屬性
    if (args == void 0) { //沒有傳入?yún)?shù)直接執(zhí)行
        return context[fn]()
    }
    var fnStr = "context[fn]("
    for (var i = 0; i < args.length; i++) {
        //得到"context.fn(arg1,arg2,arg3...)"這個(gè)字符串在,最后用eval執(zhí)行
        fnStr += i == args.length - 1 ? args[i] : args[i] + ","
    }
    fnStr += ")"
    var returnValue = eval(fnStr) //還是eval強(qiáng)大
    delete context[fn] //執(zhí)行完畢之后刪除這個(gè)屬性
    return returnValue
}

好緊張,再來做個(gè)小測試,demo,應(yīng)該不會(huì)出問題:

var obj = {
    name: "jawil"
}

function sayHello(age) {
    return {
        name: this.name,
        age: age
    }
}

console.log(sayHello.applyFive(obj,[24]));// 完美輸出{name: "jawil", age: 24}

到此,我們完成了apply的模擬實(shí)現(xiàn),給自己一個(gè)贊 b( ̄▽ ̄)d

實(shí)現(xiàn)Call方法

這個(gè)不需要講了吧,道理都一樣,就是參數(shù)一樣,這里我給出我實(shí)現(xiàn)的一種方式,看不懂,自己寫一個(gè)去。

//原生JavaScript封裝call方法
Function.prototype.callOne = function(context) {
    return this.applyFive(([].shift.applyFive(arguments), arguments) 
    //巧妙地運(yùn)用上面已經(jīng)實(shí)現(xiàn)的applyFive函數(shù)
}

看不太明白也不能怪我咯,我就不細(xì)講了,看個(gè)demo證明一下,這個(gè)寫法沒問題。

Function.prototype.applyFive = function(context) {//剛才寫的一大串}

Function.prototype.callOne = function(context) {
    return this.applyFive(([].shift.applyFive(arguments)), arguments)
    //巧妙地運(yùn)用上面已經(jīng)實(shí)現(xiàn)的applyFive函數(shù)
}

//測試一下
var obj = {
    name: "jawil"
}

function sayHello(age) {
    return {
        name: this.name,
        age: age
    }
}

console.log(sayHello.callOne(obj,24));// 完美輸出{name: "jawil", age: 24}
實(shí)現(xiàn)bind方法

養(yǎng)兵千日,用兵一時(shí)。

什么是bind函數(shù)

如果掌握了上面實(shí)現(xiàn)apply的方法,我想理解起來模擬實(shí)現(xiàn)bind方法也是輕而易舉,原理都差不多,我們還是來看看bind方法的定義。

我們還是簡單的看下ECMAScript規(guī)范對(duì)bind方法的定義,暫時(shí)看不懂不要緊,獲取幾個(gè)關(guān)鍵信息就行。

15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, …]])

注意一點(diǎn),ECMAScript規(guī)范提到: Function.prototype.bind 創(chuàng)建的函數(shù)對(duì)象不包含 prototype 屬性或 [[Code]], [[FormalParameters]], [[Scope]] 內(nèi)部屬性。

bind() 方法會(huì)創(chuàng)建一個(gè)新函數(shù),當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí),它的 this 值是傳遞給 bind() 的第一個(gè)參數(shù), 它的參數(shù)是 bind() 的其他參數(shù)和其原本的參數(shù),bind返回的綁定函數(shù)也能使用new操作符創(chuàng)建對(duì)象:這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。提供的this值被忽略,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)。。

語法是這樣樣子的:fun.bind(thisArg[, arg1[, arg2[, ...]]])

呃呃呃,是不是似曾相識(shí),這不是call方法的語法一個(gè)樣子么,,,但它們是一樣的嗎?

bind方法傳遞給調(diào)用函數(shù)的參數(shù)可以逐個(gè)列出,也可以寫在數(shù)組中。bind方法與call、apply最大的不同就是前者返回一個(gè)綁定上下文的函數(shù),而后兩者是直接執(zhí)行了函數(shù)。由于這個(gè)原因,上面的代碼也可以這樣寫:

jawil.sayHello.bind(lulin)(24); //hello, i am lulin 24 years old
jawil.sayHello.bind(lulin)([24]); //hello, i am lulin 24 years old

bind方法還可以這樣寫 fn.bind(obj, arg1)(arg2).

用一句話總結(jié)bind的用法:該方法創(chuàng)建一個(gè)新函數(shù),稱為綁定函數(shù),綁定函數(shù)會(huì)以創(chuàng)建它時(shí)傳入bind方法的第一個(gè)參數(shù)作為this,傳入bind方法的第二個(gè)以及以后的參數(shù)加上綁定函數(shù)運(yùn)行時(shí)本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)。

bind在實(shí)際中的應(yīng)用

實(shí)際使用中我們經(jīng)常會(huì)碰到這樣的問題:

function Person(name){
 this.nickname = name;
 this.distractedGreeting = function() {
 
   setTimeout(function(){
     console.log("Hello, my name is " + this.nickname);
   }, 500);
 }
}
 
var alice = new Person("jawil");
alice.distractedGreeting();
//Hello, my name is undefined

這個(gè)時(shí)候輸出的this.nickname是undefined,原因是this指向是在運(yùn)行函數(shù)時(shí)確定的,而不是定義函數(shù)時(shí)候確定的,再因?yàn)閟etTimeout在全局環(huán)境下執(zhí)行,所以this指向setTimeout的上下文:window。關(guān)于this指向問題,這里就不細(xì)扯

以前解決這個(gè)問題的辦法通常是緩存this,例如:

function Person(name){
  this.nickname = name;
  this.distractedGreeting = function() {
    var self = this; // <-- 注意這一行!
    setTimeout(function(){
      console.log("Hello, my name is " + self.nickname); // <-- 還有這一行!
    }, 500);
  }
}
 
var alice = new Person("jawil");
alice.distractedGreeting();
// after 500ms logs "Hello, my name is jawil"

這樣就解決了這個(gè)問題,非常方便,因?yàn)樗沟胹etTimeout函數(shù)中可以訪問Person的上下文。但是看起來稍微一種蛋蛋的憂傷。

但是現(xiàn)在有一個(gè)更好的辦法!您可以使用bind。上面的例子中被更新為:

function Person(name){
  this.nickname = name;
  this.distractedGreeting = function() {
    setTimeout(function(){
      console.log("Hello, my name is " + this.nickname);
    }.bind(this), 500); // <-- this line!
  }
}
 
var alice = new Person("jawil");
alice.distractedGreeting();
// after 500ms logs "Hello, my name is jawil"

bind() 最簡單的用法是創(chuàng)建一個(gè)函數(shù),使這個(gè)函數(shù)不論怎么調(diào)用都有同樣的 this 值。JavaScript新手經(jīng)常犯的一個(gè)錯(cuò)誤是將一個(gè)方法從對(duì)象中拿出來,然后再調(diào)用,希望方法中的 this 是原來的對(duì)象。(比如在回調(diào)中傳入這個(gè)方法。)如果不做特殊處理的話,一般會(huì)丟失原來的對(duì)象。從原來的函數(shù)和原來的對(duì)象創(chuàng)建一個(gè)綁定函數(shù),則能很漂亮地解決這個(gè)問題:

this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};
 
module.getX(); // 81
 
var getX = module.getX;
getX(); // 9, 因?yàn)樵谶@個(gè)例子中,"this"指向全局對(duì)象
 
// 創(chuàng)建一個(gè)"this"綁定到module的函數(shù)
var boundGetX = getX.bind(module);
boundGetX(); // 81

很不幸,F(xiàn)unction.prototype.bind 在IE8及以下的版本中不被支持,所以如果你沒有一個(gè)備用方案的話,可能在運(yùn)行時(shí)會(huì)出現(xiàn)問題。bind 函數(shù)在 ECMA-262 第五版才被加入;它可能無法在所有瀏覽器上運(yùn)行。你可以部份地在腳本開頭加入以下代碼,就能使它運(yùn)作,讓不支持的瀏覽器也能使用 bind() 功能。

幸運(yùn)的是,我們可以自己來模擬bind功能:

初級(jí)實(shí)現(xiàn)

了解了以上內(nèi)容,我們來實(shí)現(xiàn)一個(gè)初級(jí)的bind函數(shù)Polyfill:

Function.prototype.bind = function (context) {
    var me = this;
    var argsArray = Array.prototype.slice.callOne(arguments);
    return function () {
        return me.applyFive(context, argsArray.slice(1))
    }
}

我們先簡要解讀一下:
基本原理是使用apply進(jìn)行模擬。函數(shù)體內(nèi)的this,就是需要綁定this的實(shí)例函數(shù),或者說是原函數(shù)。最后我們使用apply來進(jìn)行參數(shù)(context)綁定,并返回。
同時(shí),將第一個(gè)參數(shù)(context)以外的其他參數(shù),作為提供給原函數(shù)的預(yù)設(shè)參數(shù),這也是基本的“顆?;╟urring)”基礎(chǔ)。

初級(jí)實(shí)現(xiàn)的加分項(xiàng)

上面的實(shí)現(xiàn)(包括后面的實(shí)現(xiàn)),其實(shí)是一個(gè)典型的“Monkey patching(猴子補(bǔ)丁)”,即“給內(nèi)置對(duì)象擴(kuò)展方法”。所以,如果面試者能進(jìn)行一下“嗅探”,進(jìn)行兼容處理,就是錦上添花了。

Function.prototype.bind = Function.prototype.bind || function (context) {
    ...
}
顆?;╟urring)實(shí)現(xiàn)

對(duì)于函數(shù)的柯里化不太了解的童鞋,可以先嘗試讀讀這篇文章:前端基礎(chǔ)進(jìn)階(八):深入詳解函數(shù)的柯里化。
上述的實(shí)現(xiàn)方式中,我們返回的參數(shù)列表里包含:atgsArray.slice(1),他的問題在于存在預(yù)置參數(shù)功能丟失的現(xiàn)象。
想象我們返回的綁定函數(shù)中,如果想實(shí)現(xiàn)預(yù)設(shè)傳參(就像bind所實(shí)現(xiàn)的那樣),就面臨尷尬的局面。真正實(shí)現(xiàn)顆?;摹巴昝婪绞健笔牵?/p>

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.callOne(arguments, 1);
    return function () {
        var innerArgs = Array.prototype.slice.callOne(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.applyFive(context, finalArgs);
    }
}

上面什么是bind函數(shù)還介紹到:bind返回的函數(shù)如果作為構(gòu)造函數(shù),搭配new關(guān)鍵字出現(xiàn)的話,我們的綁定this就需要“被忽略”。

構(gòu)造函數(shù)場景下的兼容

有了上邊的講解,不難理解需要兼容構(gòu)造函數(shù)場景的實(shí)現(xiàn):

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.callOne(arguments, 1);
    var F = function () {};
    F.prototype = this.prototype;
    var bound = function () {
        var innerArgs = Array.prototype.slice.callOne(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(this instanceof F ? this : context || this, finalArgs);
    }
    bound.prototype = new F();
    return bound;
}
更嚴(yán)謹(jǐn)?shù)淖龇?/b>

我們需要調(diào)用bind方法的一定要是一個(gè)函數(shù),所以可以在函數(shù)體內(nèi)做一個(gè)判斷:

if (typeof this !== "function") {
  throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}

做到所有這一切,基本算是完成了。其實(shí)MDN上有個(gè)自己實(shí)現(xiàn)的polyfill,就是如此實(shí)現(xiàn)的。
另外,《JavaScript Web Application》一書中對(duì)bind()的實(shí)現(xiàn),也是如此。

最終答案
//簡單模擬Symbol屬性
function jawilSymbol(obj) {
    var unique_proper = "00" + Math.random();
    if (obj.hasOwnProperty(unique_proper)) {
        arguments.callee(obj)//如果obj已經(jīng)有了這個(gè)屬性,遞歸調(diào)用,直到?jīng)]有這個(gè)屬性
    } else {
        return unique_proper;
    }
}
//原生JavaScript封裝apply方法,第五版
Function.prototype.applyFive = function(context) {
    var context = context || window
    var args = arguments[1] //獲取傳入的數(shù)組參數(shù)
    var fn = jawilSymbol(context);
    context[fn] = this //假想context對(duì)象預(yù)先不存在名為fn的屬性
    if (args == void 0) { //沒有傳入?yún)?shù)直接執(zhí)行
        return context[fn]()
    }
    var fnStr = "context[fn]("
    for (var i = 0; i < args.length; i++) {
        //得到"context.fn(arg1,arg2,arg3...)"這個(gè)字符串在,最后用eval執(zhí)行
        fnStr += i == args.length - 1 ? args[i] : args[i] + ","
    }
    fnStr += ")"
    var returnValue = eval(fnStr) //還是eval強(qiáng)大
    delete context[fn] //執(zhí)行完畢之后刪除這個(gè)屬性
    return returnValue
}
//簡單模擬call函數(shù)
Function.prototype.callOne = function(context) {
    return this.applyFive(([].shift.applyFive(arguments)), arguments)
    //巧妙地運(yùn)用上面已經(jīng)實(shí)現(xiàn)的applyFive函數(shù)
}

//簡單模擬bind函數(shù)
Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.callOne(arguments, 1);
    var F = function () {};
    F.prototype = this.prototype;
    var bound = function () {
        var innerArgs = Array.prototype.slice.callOne(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.applyFive(this instanceof F ? this : context || this, finalArgs);
    }
    bound.prototype = new F();
    return bound;
}

好緊張,最后來做個(gè)小測試,demo,應(yīng)該不會(huì)出問題:

var obj = {
    name: "jawil"
}

function sayHello(age) {
    return {
        name: this.name,
        age: age
    }
}

console.log(sayHello.bind(obj,24)());// 完美輸出{name: "jawil", age: 24}

看了這篇文章,以后再遇到類似的問題,應(yīng)該能夠順利通過吧~

參考文章

ES6入門之Symbol
ECMAScript 5.1(英文版)
從一道面試題,到“我可能看了假源碼”

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/111951.html

相關(guān)文章

  • 可能遇到面試不用callapply方法模擬實(shí)現(xiàn)ES5bind方法

    摘要:來自朋友去某信用卡管家的做的一道面試題,用原生模擬的方法,不準(zhǔn)用和方法。他們的用途相同,都是在特定的作用域中調(diào)用函數(shù)。不同之處在于,方法傳遞給調(diào)用函數(shù)的參數(shù)是逐個(gè)列出的,而則是要寫在數(shù)組中。 本文首發(fā)我的個(gè)人博客:前端小密圈,評(píng)論交流送1024邀請(qǐng)碼,嘿嘿嘿?。 來自朋友去某信用卡管家的做的一道面試題,用原生JavaScript模擬ES5的bind方法,不準(zhǔn)用call和bind方法。 ...

    ConardLi 評(píng)論0 收藏0
  • javasscript - 收藏集 - 掘金

    摘要:跨域請(qǐng)求詳解從繁至簡前端掘金什么是為什么要用是的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題。異步編程入門道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 jsonp 跨域請(qǐng)求詳解——從繁至簡 - 前端 - 掘金什么是jsonp?為什么要用jsonp?JSONP(JSON with Padding)是JSON的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題...

    Rango 評(píng)論0 收藏0
  • 從一道面試,到“我可能看了假源碼”

    摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對(duì)象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。同時(shí),將第一個(gè)參數(shù)以外的其他參數(shù),作為提供給原函數(shù)的預(yù)設(shè)參數(shù),這也是基本的顆?;A(chǔ)。 今天想談?wù)勔坏狼岸嗣嬖囶},我做面試官的時(shí)候經(jīng)常喜歡用它來考察面試者的基礎(chǔ)是否扎實(shí),以及邏輯、思維能力和臨場表現(xiàn),題目是:模擬實(shí)現(xiàn)ES5中原生bind函數(shù)。也許這道題目已經(jīng)不再新鮮,部分讀者也會(huì)有思路來解答。社區(qū)上關(guān)于原生bind的研...

    Carson 評(píng)論0 收藏0
  • 從一道面試,到“我可能看了假源碼”

    摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對(duì)象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。同時(shí),將第一個(gè)參數(shù)以外的其他參數(shù),作為提供給原函數(shù)的預(yù)設(shè)參數(shù),這也是基本的顆?;A(chǔ)。 今天想談?wù)勔坏狼岸嗣嬖囶},我做面試官的時(shí)候經(jīng)常喜歡用它來考察面試者的基礎(chǔ)是否扎實(shí),以及邏輯、思維能力和臨場表現(xiàn),題目是:模擬實(shí)現(xiàn)ES5中原生bind函數(shù)。也許這道題目已經(jīng)不再新鮮,部分讀者也會(huì)有思路來解答。社區(qū)上關(guān)于原生bind的研...

    rockswang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<