前言
这一篇是直接学习 JavaScript深入之call和apply的模拟实现和JavaScript深入之bind的模拟实现的,建议直接点开链接学习!
call
call()方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。
第一版
var foo = {
value: 1
};
function bar(){
console.log(this.foo);
}
bar.call(foo); // 1
可以将这个例子转换为:
var foo = {
value: 1,
bar: function(){
console.log(this.value);
}
}
// 然后调用之后,删除bar就行
所以实现如下:
Function.prototype.call2 = function(context){
context.fn = this;
context.fn();
delete context.fn;
}
第二版
call
要处理传参的问题:
var foo = {
value: 1
}
function bar(name, age){
console.log(name);
console.log(age);
console.log(this.value);
}
bar.call(foo, 'sugarMei', 18); // sugarMei 18 1
函数内部自带有一个arguments
参数,里面记录了所有的参数,形式如下:
arguments = {
0: foo,
1: "sugarMei",
2: 18,
length: 3
}
所以我们需要将除了第一个之外的所有参数保存起来,传给另一个函数,然后借助eval
:
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(let i = 1, len = arguments.length; i < len; i++){
args.push('arguments['+i+']');
}
eval('context.fn('+args+')');
delete context.fn;
}
第三版
考虑一下如果this
传null
,和带有返回值的情况:
Function.prototype.call2 = function(context){
context = context || window;
context.fn = this;
var args = [];
for(let i = 1, len = arguments.length; i < len; i++){
args.push('arguments[' + i + ']');
}
var result = eval('context.fn('+ args +')');
delete context.fn;
return result;
}
apply
Function.prototype.apply2 = function (context, arr) {
context = context || window;
context.fn = this;
var result;
if(!arr){
result = context.fn();
}else{
var args = [];
for(let i = 0, len = arr.length; i < len; i++){
args.push('arguments['+i+']');
}
result = eval('context.fn('+args+')');
}
delete context.fn;
return result;
}
bind
bind方法会创建一个新函数,当这个新函数被调用时,bind的第一个参数将作为它运行时的this,之后的一序列参数会在传递的实参前传入作为它的参数。(来自于 MDN )
第一版
举个例子:
var foo = {
value: 1
}
function bar(){
console.log(this.value);
}
var bindFoo = bar.bind(foo);
bindFoo(); // 1
我们需要做的就是借用apply
或者call
返回一个新函数:
Function.prototype.bind2 = function(context){
var self = this;
return function(){
return self.apply(context);
}
}
第二版
bind
函数支持传参:
var foo = {
value: 1
}
var bar(name, age){
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo(18);
// 1
// daisy
// 18
在这里,可以在使用bind
的时候传递参数,在执行的时候还能再传递参数。
Function.prototype.bind2 = function(context){
var self = this;
var args = Array.prototype.slice(arguments, 1);
return function(){
var bindArgs = Array.prototype.slice(arguments);
return this.apply(context, args.concat(bindArgs));
}
}
第三版
使用bind
之后会返回一个新的函数,比如上面的bindFoo
中this
指向的是foo
,但是如果bindFoo
被当做构造函数使用的时候呢?this
指向的是谁?
在前面的学习中我们已经知道,this
的指向优先级:箭头函数绑定 > new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定,即new
绑定的优先级比bind
这种显示绑定高;而且使用new
时,实例的__proto__
会指向构造函数的prototype
,实例的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 = 'sugarMei';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo(18);
// undefined
// daisy
// 18
console.log(obj.habbit); // shopping
console.log(obj.friend); // sugarMei
由上面的栗子可以发现var obj = new binFoo(18);
中,由于this
指向实例,但是它并不存在value
值,所以返回undefined
。
这里我们需要修改返回函数的原型:
Function.prototype.bind2 = function(context){
var self = this;
var args = Array.prototype.slice(arguments, 1);
var fBound = function(){
var bindArgs = Array.prototype.slice(arguments);
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
// 确保还是可以读取到原来的prototype上的属性
fBound.prototype = self.prototype;
return fBound;
}
第四版
如果使用上面的例子:
var foo = {
value: 1
}
function bar(name, age){
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'sugarMei';
var bindFoo = bar.bind2(foo, 'daisy');
bindFoo.prototype.fiend = '王花花';
console.log(bar.prototype.fiend); // 王花花
修改bindFoo.prototype
上的属性,也会影响到bar.prototype
,这里需要做一些调整:
Function.prototype.bind2 = function(context){
var self = this;
var args = Array.prototype.slice(arguments, 1);
var fNOP = function(){};
var fBound = function(){
var bindArgs = Array.prototype.slice(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
最终版
如果调用bind
的不是函数,就要抛出异常:
Function.prototype.bind = 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(arguments, 1);
var fNOP = function(){};
var fBound = function(){
var bindArgs = Array.prototype.slice(arguments);
return self.apply(this instanceof fNOP? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype
fBound.prototype = new fNOP();
return fBound;
}