JavaScript 中call蔼夜、apply、bind压昼、new的模擬實(shí)現(xiàn)

【call】

call() 方法在使用一個(gè)指定的 this 值和若干個(gè)指定的參數(shù)值的前提下調(diào)用某個(gè)函數(shù)或方法求冷。

舉例:

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1

注意兩點(diǎn):

  • call 改變了 this 的指向,指向到 foo
  • bar函數(shù)執(zhí)行了

第一步:改變this指向

我們模擬的步驟可以分為:

  • 將函數(shù)設(shè)為對(duì)象的屬性
  • 執(zhí)行該函數(shù)
  • 刪除該函數(shù)
// 第一版
Function.prototype.call2 = function(context) {
    // 首先要獲取調(diào)用call的函數(shù)窍霞,用this可以獲取
    context.fn = this;
    context.fn();
    delete context.fn;
}

// 測(cè)試一下
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call2(foo); // 1

第二步:call 函數(shù)還能給定參數(shù)執(zhí)行函數(shù)

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}

// 測(cè)試一下
var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1

第三步:this 參數(shù)可以傳 null匠题,當(dāng)為 null 的時(shí)候,視為指向 window但金;函數(shù)是可以有返回值的

/ 第三版
Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

// 測(cè)試一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar.call(null); // 2

console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

【apply】

apply() 方法在使用一個(gè)指定的 this 值和若干個(gè)指定的參數(shù)構(gòu)成的數(shù)列的前提下調(diào)用某個(gè)函數(shù)或方法韭山。

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

【bind】

bind() 方法會(huì)創(chuàng)建一個(gè)新函數(shù)。當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí)傲绣,bind() 的第一個(gè)參數(shù)將作為它運(yùn)行時(shí)的 this掠哥,之后的一序列參數(shù)將會(huì)在傳遞的實(shí)參前傳入作為它的參數(shù)。

由此我們可以首先得出 bind 函數(shù)的兩個(gè)特點(diǎn):

  • 返回一個(gè)函數(shù)
  • 可以傳入?yún)?shù)

第一步:返回函數(shù)的模擬實(shí)現(xiàn)

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

// 返回了一個(gè)函數(shù)
var bindFoo = bar.bind(foo); 

bindFoo(); // 1

關(guān)于指定 this 的指向秃诵,我們可以使用 call 或者 apply 實(shí)現(xiàn)

// 第一版
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        self.apply(context);
    }

}

第二步:傳參的模擬實(shí)現(xiàn)

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);

}

var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18

函數(shù)需要傳 name 和 age 兩個(gè)參數(shù)续搀,竟然還可以在 bind 的時(shí)候,只傳一個(gè) name菠净,在執(zhí)行返回的函數(shù)的時(shí)候禁舷,再傳另一個(gè)參數(shù) age!

// 第二版
Function.prototype.bind2 = function (context) {

    var self = this;
    // 獲取bind2函數(shù)從第二個(gè)參數(shù)到最后一個(gè)參數(shù)
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
        // 這個(gè)時(shí)候的arguments是指bind返回的函數(shù)傳入的參數(shù)
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(context, args.concat(bindArgs));
    }

}

第三步:構(gòu)造函數(shù)效果的模擬實(shí)現(xiàn)

一個(gè)綁定函數(shù)也能使用new操作符創(chuàng)建對(duì)象:這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。提供的 this 值被忽略毅往,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)牵咙。

也就是說當(dāng) bind 返回的函數(shù)作為構(gòu)造函數(shù)的時(shí)候,bind 時(shí)指定的 this 值會(huì)失效攀唯,但傳入的參數(shù)依然生效洁桌。

舉個(gè)例子:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

注意:盡管在全局和 foo 中都聲明了 value 值,最后依然返回了 undefind侯嘀,說明綁定的 this 失效了另凌,如果大家了解 new 的模擬實(shí)現(xiàn)谱轨,就會(huì)知道這個(gè)時(shí)候的 this 已經(jīng)指向了 obj。

// 第三版
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        // 當(dāng)作為構(gòu)造函數(shù)時(shí)吠谢,this 指向?qū)嵗镣藭r(shí)結(jié)果為 true,將綁定函數(shù)的 this 指向該實(shí)例工坊,可以讓實(shí)例獲得來自綁定函數(shù)的值
        // 以上面的是 demo 為例献汗,如果改成 `this instanceof fBound ? null : context`,實(shí)例只是一個(gè)空對(duì)象王污,將 null 改成 this 罢吃,實(shí)例會(huì)具有 habit 屬性
        // 當(dāng)作為普通函數(shù)時(shí),this 指向 window玉掸,此時(shí)結(jié)果為 false刃麸,將綁定函數(shù)的 this 指向 context
        self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    // 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,實(shí)例就可以繼承綁定函數(shù)的原型中的值
    fBound.prototype = this.prototype;
    return fBound;
}

第四步:構(gòu)造函數(shù)效果的優(yōu)化實(shí)現(xiàn)

但是在這個(gè)寫法中司浪,我們直接將 fBound.prototype = this.prototype泊业,我們直接修改 fBound.prototype 的時(shí)候,也會(huì)直接修改綁定函數(shù)的 prototype啊易。這個(gè)時(shí)候吁伺,我們可以通過一個(gè)空函數(shù)來進(jìn)行中轉(zhuǎn):

// 第四版
Function.prototype.bind2 = function (context) {

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

第五步:調(diào)用 bind 的不是函數(shù)咋辦?

Function.prototype.bind2 = function (context) {

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

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

【new】

new 運(yùn)算符創(chuàng)建一個(gè)用戶定義的對(duì)象類型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對(duì)象類型之一

因?yàn)?new 是關(guān)鍵字租谈,所以無法像 bind 函數(shù)一樣直接覆蓋篮奄,所以我們寫一個(gè)函數(shù),命名為 objectFactory割去,來模擬 new 的效果窟却。用的時(shí)候是這樣的:

function Otaku () {
    ……
}

// 使用 new
var person = new Otaku(……);
// 使用 objectFactory
var person = objectFactory(Otaku, ……)
// 第一版代碼
function objectFactory() {

    var obj = new Object(),

    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    Constructor.apply(obj, arguments);

    return obj;

};

在這一版中,我們:

  • 用new Object() 的方式新建了一個(gè)對(duì)象 obj
  • 取出第一個(gè)參數(shù)呻逆,就是我們要傳入的構(gòu)造函數(shù)夸赫。此外因?yàn)?shift 會(huì)修改原數(shù)組,所以 arguments 會(huì)被去除第一個(gè)參數(shù)
  • 將 obj 的原型指向構(gòu)造函數(shù)咖城,這樣 obj 就可以訪問到構(gòu)造函數(shù)原型中的屬性
  • 使用 apply茬腿,改變構(gòu)造函數(shù) this 的指向到新建的對(duì)象,這樣 obj 就可以訪問到構(gòu)造函數(shù)中的屬性
  • 返回 obj

第二步:返回值效果實(shí)現(xiàn)

需要判斷返回的值是不是一個(gè)對(duì)象宜雀,如果是一個(gè)對(duì)象切平,我們就返回這個(gè)對(duì)象,如果沒有辐董,我們?cè)摲祷厥裁淳头祷厥裁础?/p>

// 第二版的代碼
function objectFactory() {

    var obj = new Object(),

    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    var ret = Constructor.apply(obj, arguments);

    return typeof ret === 'object' ? ret : obj;

};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悴品,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苔严,老刑警劉巖菇存,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異邦蜜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亥至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門悼沈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姐扮,你說我怎么就攤上這事絮供。” “怎么了茶敏?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵壤靶,是天一觀的道長。 經(jīng)常有香客問我惊搏,道長贮乳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任恬惯,我火速辦了婚禮向拆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酪耳。我一直安慰自己浓恳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布碗暗。 她就那樣靜靜地躺著颈将,像睡著了一般。 火紅的嫁衣襯著肌膚如雪言疗。 梳的紋絲不亂的頭發(fā)上晴圾,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音洲守,去河邊找鬼疑务。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梗醇,可吹牛的內(nèi)容都是我干的知允。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叙谨,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼温鸽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤涤垫,失蹤者是張志新(化名)和其女友劉穎姑尺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝠猬,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡切蟋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榆芦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柄粹。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匆绣,靈堂內(nèi)的尸體忽然破棺而出驻右,到底是詐尸還是另有隱情,我是刑警寧澤崎淳,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布堪夭,位于F島的核電站,受9級(jí)特大地震影響拣凹,放射性物質(zhì)發(fā)生泄漏森爽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一嚣镜、第九天 我趴在偏房一處隱蔽的房頂上張望拗秘。 院中可真熱鬧,春花似錦祈惶、人聲如沸雕旨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凡涩。三九已至,卻和暖如春疹蛉,著一層夾襖步出監(jiān)牢的瞬間活箕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工可款, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留育韩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓闺鲸,卻偏偏與公主長得像筋讨,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摸恍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容