理解
- call
改變函數(shù)的this指向,且為執(zhí)行時(shí)的this指向,第二個(gè)參數(shù)逐次傳入即可,在綁定之后立即執(zhí)行 - apply
改變函數(shù)的this指向,且為執(zhí)行時(shí)的this指向,第二個(gè)參數(shù)需要以數(shù)組的形式傳入即可,在綁定之后立即執(zhí)行 - bind
改變函數(shù)的this指向,但是同時(shí)生成一個(gè)綁定函數(shù),綁定函數(shù)作為返回值返回,綁定之后并不立即執(zhí)行,可以給返回的綁定函數(shù)繼續(xù)傳入?yún)?shù),并且bind的第二個(gè)參數(shù)傳入的格式類似于call。
嘗試
bind的特點(diǎn)
- 返回一個(gè)函數(shù)
- 可以繼續(xù)給返回的函數(shù)傳入?yún)?shù)
嘗試
version1
//考慮到返回的是一個(gè)函數(shù)
Function.prototype.bind = function (fn) {
return function () {
return this.call(fn);
};
};
var foo = function () {
console.log(this.color);
};
var bar = {
color: 'red'
};
var func = foo.bind(bar);
func();
執(zhí)行結(jié)果

image.png
version2
//上一步中犯了一個(gè)錯(cuò)誤導(dǎo)致執(zhí)行出錯(cuò),關(guān)于this,需要在返回函數(shù)之前做this的緩存
Function.prototype.bind = function (fn) {
var _this = this;
return function () {
return _this.apply(fn);
};
};
var foo = function () {
console.log(this.color);
};
var bar = {
color: 'red'
};
var func = foo.bind(bar);
func();
執(zhí)行結(jié)果

image.png
version3
//版本2可以正確執(zhí)行,但是不能傳入?yún)?shù)
Function.prototype.bind = function (fn) {
var _this = this,
args = Array.prototype.slice.call(arguments, 1);//截取第一個(gè)參數(shù)以后的所有參數(shù),且將它們轉(zhuǎn)為真正的數(shù)組
return function () {
return _this.apply(fn,args);
};
};
var foo = function () {
console.log(this.color);
console.log(arguments);
};
var bar = {
color: 'red'
};
var func = foo.bind(bar,1,2,3,4);
func();
執(zhí)行結(jié)果

image.png
version4
Function.prototype.bind = function (fn) {
var _this = this,
args = Array.prototype.slice.call(arguments, 1);
return function () {
var finalArgs = args.concat(Array.prototype.slice.call(arguments));//保證在返回的參數(shù)里還能傳入?yún)?shù)
return _this.apply(fn,finalArgs);
};
};
var foo = function () {
console.log(this.color);
console.log(arguments);
};
var bar = {
color: 'red'
};
var func = foo.bind(bar,1,2,3,4);
func(11,12,13,14);
延伸參考
以上是我自己寫出來的版本,但是在寫完之后去查閱了一些資料,進(jìn)行了進(jìn)一步的理解和學(xué)習(xí)
當(dāng)把bind綁定之后的函數(shù)作為構(gòu)造函數(shù)返回,則會(huì)產(chǎn)生一些和當(dāng)前編寫的版本4有差異的地方,如下。
bind
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
執(zhí)行結(jié)果

image.png
版本4加入后
//定義bind
Function.prototype.bind = function (fn) {
var _this = this,
args = Array.prototype.slice.call(arguments, 1);//截取第一個(gè)參數(shù)以后的所有參數(shù),且將它們轉(zhuǎn)為真正的數(shù)組
return function () {
var finalArgs = args.concat(Array.prototype.slice.call(arguments));
return _this.apply(fn,finalArgs);
};
};
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
執(zhí)行結(jié)果

image.png
分析:首先需要明確的是new一個(gè)對(duì)象的過程,先創(chuàng)建一個(gè)空對(duì)象,繼承構(gòu)造函數(shù)的原型,然后將this指向這個(gè)新對(duì)象,然后將該對(duì)象賦給當(dāng)前的變量即可。那么首先就要明確兩點(diǎn):1.原型繼承 2.this的指向
首先,修改this
Function.prototype.bind = function (fn) {
var _this = this,
args = Array.prototype.slice.call(arguments,1);
var boundFn = function () {
//判斷this
var finalArgs = args.concat(Array.prototype.slice.call(arguments));
return _this.apply(this instanceof boundFn ? this : fn, finalArgs);//在判斷之后替換掉原來的this
};
boundFn.prototype = this.prototype;//原型繼承
return boundFn;
};
執(zhí)行結(jié)果

image.png
結(jié)論:
可以通過判斷最終的返回函數(shù)的this的真正指向,去判斷真正的fn是哪一個(gè)函數(shù),究竟是原來的bar還是后來通過構(gòu)造函數(shù)實(shí)例化修改了this的obj。這一點(diǎn)對(duì)于最終的value判斷很重要,因?yàn)閷?shí)例化之后的obj沒有value,但是obj通過原型繼承可以訪問到bar的原型中的friend。
參考材料
總結(jié)
- 重新復(fù)習(xí)了this相關(guān)的知識(shí)點(diǎn),以及鞏固了對(duì)于apply和call的使用
- 計(jì)劃下一次整理關(guān)于原型鏈的知識(shí)點(diǎn),同時(shí)回顧原型繼承