举个例证,由此大家能够率先得出 bind 函数的八

2019-11-04 01:26栏目:前端开发
TAG:

JavaScript 深刻之call和apply的效仿达成

2017/05/25 · JavaScript · apply, call

初藳出处: 冴羽   

JavaScript 浓烈之bind的模拟达成

2017/05/26 · JavaScript · bind

原来的小说出处: 冴羽   

call

一句话介绍 call:

call() 方法在应用二个内定的 this 值和几何个指定的参数值的前提下调用某些函数或情势。

举个例证:

var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call(foo); // 1

1
2
3
4
5
6
7
8
9
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
bar.call(foo); // 1

只顾两点:

  1. call 改变了 this 的指向,指向到 foo
  2. bar 函数试行了

bind

一句话介绍 bind:

bind() 方法会创立贰个新函数。当以此新函数被调用时,bind() 的率先个参数将用作它运营时的 this,之后的生机勃勃体系参数将会在传递的实参前流传作为它的参数。(来自于 MDN )

透过大家得以率先得出 bind 函数的三个特点:

  1. 回去多个函数
  2. 可以流传参数

如法泡制完毕率先步

那么大家该怎么模拟完毕那多少个功用啊?

试想当调用 call 的时候,把 foo 对象更动成如下:

var foo = { value: 1, bar: function() { console.log(this.value) } }; foo.bar(); // 1

1
2
3
4
5
6
7
8
var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};
 
foo.bar(); // 1

本条时候 this 就针对了 foo,是否很简短吗?

但是那样却给 foo 对象自己增加了五性情能,那可非常啊!

但是也不用忧郁,大家用 delete 再删除它不就好了~

由此大家模拟的步子能够分为:

  1. 将函数设为对象的习性
  2. 实行该函数
  3. 删去该函数

如上个例证为例,正是:

// 第一步 foo.fn = bar // 第二步 foo.fn() // 第三步 delete foo.fn

1
2
3
4
5
6
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn

fn 是目的的属性名,反正最后也要删减它,所以起成如何都不留意。

基于那一个思路,我们得以尝尝着去写第生机勃勃版的 call2 函数:

// 第风流罗曼蒂克版 Function.prototype.call2 = function(context) { // 首先要赢得调用call的函数,用this能够获得 context.fn = this; context.fn(); delete context.fn; } // 测验一下 var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call2(foo); // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}
 
// 测试一下
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
bar.call2(foo); // 1

无唯有偶能够打字与印刷 1 哎!是否很欢腾!(~ ̄▽ ̄)~

回到函数的模拟完毕

从第叁个特色先导,我们比方:

var foo = { value: 1 }; function bar() { console.log(this.value); } // 再次来到了八个函数 var bindFoo = bar.bind(foo); bindFoo(); // 1

1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
// 返回了一个函数
var bindFoo = bar.bind(foo);
 
bindFoo(); // 1

有关钦定 this 的针对,大家得以行使 call 大概 apply 贯彻,关于 call 和 apply 的模拟完毕,可以查看《JavaScript深切之call和apply的效仿完成》。大家来写第生机勃勃版的代码:

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

1
2
3
4
5
6
7
8
// 第一版
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        self.apply(context);
    }
 
}

仿照完毕第二步

最大器晚成开首也讲了,call 函数还能给定参数实施函数。比如:

var foo = { value: 1 }; function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } bar.call(foo, 'kevin', 18); // kevin // 18 // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var foo = {
    value: 1
};
 
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
 
bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1

瞩目:传入的参数并不鲜明,那可如何是好?

不急,我们得以从 Arguments 对象中取值,抽出第叁个到最后一个参数,然后嵌入贰个数组里。

举个例子说那样:

// 以上个例证为例,那时的arguments为: // arguments = { // 0: foo, // 1: 'kevin', // 2: 18, // length: 3 // } // 因为arguments是类数组对象,所以能够用for循环 var args = []; for(var i = 1, len = arguments.length; i len; i++) { args.push('arguments[' + i + ']'); } // 执行后 args为 [foo, 'kevin', 18]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 以上个例子为例,此时的arguments为:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i  len; i++) {
    args.push('arguments[' + i + ']');
}
 
// 执行后 args为 [foo, 'kevin', 18]

不定长的参数难题化解了,大家随后要把这些参数数组放到要施行的函数的参数里面去。

// 将数组里的成分作为多个参数放进函数的形参里 context.fn(args.join(',')) // (O_o)?? // 那么些法子明确是十三分的呀!!!

1
2
3
4
// 将数组里的元素作为多个参数放进函数的形参里
context.fn(args.join(','))
// (O_o)??
// 这个方法肯定是不行的啦!!!

或是有人想到用 ES6 的办法,可是 call 是 ES3 的点子,大家为了模仿达成四个ES3 的主意,要用到ES6的秘诀,好像……,嗯,也能够啊。不过大家此番用 eval 方法拼成三个函数,相似于那般:

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

1
eval('context.fn(' + args +')')

那边 args 会自动调用 Array.toString() 那几个模式。

由此大家的第二版击溃了七个大主题素材,代码如下:

// 第二版 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; } // 测量检验一下 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 第二版
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;
}
 
// 测试一下
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

(๑•̀ㅂ•́)و✧

传参的效仿完毕

接下去看第二点,能够流传参数。这么些就有一点点令人费解了,小编在 bind 的时候,是不是足以传参呢?笔者在举办 bind 再次回到的函数的时候,可不得以传参呢?让我们看个例子:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

函数必要传 name 和 age 八个参数,竟然还足以在 bind 的时候,只传一个name,在实践回来的函数的时候,再传另三个参数 age!

那可怎么做?不急,大家用 arguments 举行拍卖:

// 第二版 Function.prototype.bind2 = function (context) { var self = this; // 获取bind2函数从第二个参数到最后三个参数 var args = Array.prototype.slice.call(arguments, 1); return function () { // 此时的arguments是指bind重临的函数字传送入的参数 var bindArgs = Array.prototype.slice.call(arguments); self.apply(context, args.concat(bindArgs)); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第二版
Function.prototype.bind2 = function (context) {
 
    var self = this;
    // 获取bind2函数从第二个参数到最后一个参数
    var args = Array.prototype.slice.call(arguments, 1);
 
    return function () {
        // 这个时候的arguments是指bind返回的函数传入的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(context, args.concat(bindArgs));
    }
 
}

效仿完毕第三步

宪章代码已经到位 十分之七,还会有四个小点要在乎:

1.this 参数能够传 null,当为 null 的时候,视为指向 window

举例:

var value = 1; function bar() { console.log(this.value); } bar.call(null); // 1

1
2
3
4
5
6
7
var value = 1;
 
function bar() {
    console.log(this.value);
}
 
bar.call(null); // 1

纵然这几个事例自个儿不应用 call,结果仍旧同样。

2.函数是足以有再次来到值的!

比如:

var obj = { value: 1 } function bar(name, age) { return { value: this.value, name: name, age: age } } console.log(bar.call(obj, 'kevin', 18)); // Object { // value: 1, // name: 'kevin', // age: 18 // }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
    value: 1
}
 
function bar(name, age) {
    return {
        value: this.value,
        name: name,
        age: age
    }
}
 
console.log(bar.call(obj, 'kevin', 18));
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

但是都很好消除,让我们一向看第三版相当于最终风度翩翩版的代码:

// 第三版 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; } // 测量试验一下 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 // }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 第三版
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;
}
 
// 测试一下
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
// }

到此,大家做到了 call 的依葫芦画瓢实现,给本身二个赞 b( ̄▽ ̄卡塔尔国d

构造函数效果的比葫芦画瓢完结

做到了这两点,最难的有的到啦!因为 bind 还应该有二个表征,就是

一个绑定函数也能采取new操作符创制对象:这种作为就像把原函数当成构造器。提供的 this 值被忽略,相同的时候调用时的参数被提要求模拟函数。

也正是说当 bind 重返的函数作为构造函数的时候,bind 时内定的 this 值会失效,但传播的参数依旧奏效。例如:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 的模拟完结,就可以预知这时的 this 已经针对性了 obj。

(哈哈,笔者那是为小编的下生龙活虎篇随笔《JavaScript浓重体系之new的模拟完毕》打广告)。

由此大家得以经过改换再次回到的函数的原型来完成,让咱们写一下:

// 第三版 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); // 当作为构造函数时,this 指向实例,self 指向绑定函数,因为上面一句 `fbound.prototype = this.prototype;`,已经修改了 fbound.prototype 为 绑定函数的 prototype,那时结果为 true,当结果为 true 的时候,this 指向实例。 // 充当为常常函数时,this 指向 window,self 指向绑定函数,当时结果为 false,当结果为 false 的时候,this 指向绑定的 context。 self.apply(this instanceof self ? this : context, args.concat(bindArgs)); } // 修正再次回到函数的 prototype 为绑定函数的 prototype,实例就足以三回九转函数的原型中的值 fbound.prototype = this.prototype; return fbound; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 第三版
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);
        // 当作为构造函数时,this 指向实例,self 指向绑定函数,因为下面一句 `fbound.prototype = this.prototype;`,已经修改了 fbound.prototype 为 绑定函数的 prototype,此时结果为 true,当结果为 true 的时候,this 指向实例。
        // 当作为普通函数时,this 指向 window,self 指向绑定函数,此时结果为 false,当结果为 false 的时候,this 指向绑定的 context。
        self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    }
    // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
    fbound.prototype = this.prototype;
    return fbound;
}

万少年老成对原型链稍有质疑,能够查看《JavaScript浓烈之从原型到原型链》。

apply的效仿实现

apply 的完毕跟 call 相同,在那处平素给代码,代码来自于新浪 @郑航的得以完结:

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; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

构造函数效果的优化实现

只是在这里个写法中,大家直接将 fbound.prototype = this.prototype,我们一贯更改 fbound.prototype 的时候,也会平昔改进函数的 prototype。这时,大家得以因此三个空函数来举办转账:

// 第四版 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 self ? this : context, args.concat(bindArgs)); } fNOP.prototype = this.prototype; fbound.prototype = new fNOP(); return fbound; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 第四版
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 self ? this : context, args.concat(bindArgs));
    }
    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();
    return fbound;
 
}

到此结束,大的标题都早就化解,给协和二个赞!o( ̄▽ ̄)d

最首要参照

知乎难题 不可能应用call、apply、bind,如何用 js 达成 call 也许 apply 的效应?

八个没失常

接下去管理些小意思:

1.apply 这段代码跟 MDN 上的稍有不相同

在 MDN 粤语版讲 bind 的比葫芦画瓢完结时,apply 这里的代码是:

self.apply(this instanceof self ? this : context || this, args.concat(bindArgs))

1
self.apply(this instanceof self ? this : context || this, args.concat(bindArgs))

多了一个关于 context 是还是不是留存的剖断,但是那么些是荒谬的!

比如:

var value = 2; var foo = { value: 1, bar: bar.bind(null) }; function bar() { console.log(this.value); } foo.bar() // 2

1
2
3
4
5
6
7
8
9
10
11
var value = 2;
var foo = {
    value: 1,
    bar: bar.bind(null)
};
 
function bar() {
    console.log(this.value);
}
 
foo.bar() // 2

以上代码通常情形下会打字与印刷 2,假如换到了 context || this,这段代码就能够打字与印刷1!

之所以那边不应有进行 context 的判定,大家查看 MDN 同样内容的希伯来语版,就不设有那几个判定!

2.调用 bind 的不是函数如何是好?

十分,大家要报错!

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

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

3.自己要在线上用

那别忘了做个门户相当:

Function.prototype.bind = Function.prototype.bind || function () { …… };

1
2
3
Function.prototype.bind = Function.prototype.bind || function () {
    ……
};

理所必然最棒是用es5-shim啦。

深远系列

JavaScript深入体系目录地址:。

JavaScript浓烈体系猜想写十八篇左右,目的在于帮我们捋顺JavaScript底层知识,注重教学如原型、成效域、推行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、世袭等难题概念。

如若有错误大概十分的大心的地方,请必须赋予指正,十二分多谢。尽管喜欢依旧有所启示,接待star,对小编也是意气风发种鞭挞。

本系列:

  1. JavaScirpt 深切之从原型到原型链
  2. JavaScript 深切之词法作用域和动态功用域
  3. JavaScript 长远之奉行上下文栈
  4. JavaScript 深切之变量对象
  5. JavaScript 深远之功用域链
  6. JavaScript 深远之从 ECMAScript 标准解读 this
  7. JavaScript 深远之实行上下文
  8. JavaScript 深切之闭包
  9. JavaScript 深远之参数按值传递

    1 赞 收藏 评论

图片 1

末尾代码

由此最末尾的代码就是:

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 () { self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments))); } fNOP.prototype = this.prototype; fbound.prototype = new fNOP(); return fbound; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 () {
        self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
    }
 
    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();
 
    return fbound;
 
}

长远类别

JavaScript深刻种类目录地址:。

JavaScript深切连串猜想写十四篇左右,目的在于帮大家捋顺JavaScript底层知识,入眼传授如原型、功能域、施行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、世襲等难点概念。

如若有错误也许不事缓则圆的地方,请必须赋予指正,非常的多谢。就算喜欢照旧具备启示,招待star,对小编也是大器晚成种驱策。

本系列:

  1. JavaScirpt 长远之从原型到原型链
  2. JavaScript 深刻之词法作用域和动态效率域
  3. JavaScript 深切之实施上下文栈
  4. JavaScript 深远之变量对象
  5. JavaScript 深远之作用域链
  6. JavaScript 深刻之从 ECMAScript 规范解读 this
  7. JavaScript 深入之实践上下文
  8. JavaScript 深远之闭包
  9. JavaScript 浓厚之参数按值传递
  10. JavaScript 深刻之call和apply的依葫芦画瓢完成

    1 赞 收藏 评论

图片 2

版权声明:本文由大奖888-www.88pt88.com-大奖888官网登录发布于前端开发,转载请注明出处:举个例证,由此大家能够率先得出 bind 函数的八