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