逐步实现bind方法的特性
一、主要作用:改变执行上下文,并返回bind后的方法。(利用函数柯里化)
Function.prototype.bind = function(context){
var self = this;
return function(){
self.apply(context);
}
}
//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
name: "hua",
age: 30
}
function foo(){
console.log(this.name); //"hua"
console.log(this.age); //30
}
var bFoo = foo.bind(obj);
bFoo();
call/apply/bind其实都是改变上下方执行还境的方法,但各有不同,简单说一下call/apply不同之处
function add(a, b){
console.log(this);//{name: "shi"}
return a + b;
}
var obj = {
name: "shi"
}
add.call(obj, 1, 2); //3
add.apply(obj, [1, 2]); //3
上面代码可以看出call/apply不同之处,call(context,参数1,参数2),apply(context,[参数1,参数数2])
二、传参
- bind时传参
- 执行时传参
Function.prototype.bind = function(context){
var self = this,
//缓存原型方法,提升性能。
slice = Array.prototype.slice,
//获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
args = slice.call(arguments, 1);
return function(){
//用bind时的参数合并执行时的参数,等于所有参数
self.apply(context, args.concat(slice.call(arguments)));
}
}
//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
name: "hua",
age: 30
}
function foo(p1){
console.log(this.name); //"hua"
console.log(Array.prototype.slice.call(arguments)); //[1, 2, 2, 3, 2]
console.log(p1) //1
}
var bFoo = foo.bind(obj, 1, 2);
bFoo(2,3,2);
三. bind后的方法可以作为构造函数,也就是可以使用new操作符。
-
bind时的context值会失效
-
bind时的参数变为构造函数参数
Function.prototype.bind = function(context){
var self = this,
//缓存原型方法,提升性能。
slice = Array.prototype.slice,
//获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
args = slice.call(arguments, 1),
//返回函数
fBound = function(){
self.apply(
//如果是new操作符实例出来对象,则为this。
//如果是直接执行方法,则为context
this instanceof self ? this : context,
//用bind时的参数合并执行时的参数,等于所有参数
args.concat(slice.call(arguments))
);
}
//让返回函数拥有bind函数的原型。
fBound.prototype = this.prototype;
return fBound;
}
//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
name: "hua",
age: 30
}
function foo(p1){
console.log(this.name); //undefined
console.log(Array.prototype.slice.call(arguments)); //[1, 2, 2, 3, 2]
console.log(p1) //1
}
var bFoo = foo.bind(obj, 1, 2);
new bFoo(2,3,2);
一切看似还可以,但还远没有达到要求。
四、bind返回的函数是没有原型的。但bind后方法new出来的对象拥有和原函数一样的方法。
(这其实是个很有用的设计,让一切都发生在原来函数上,这样出现bug也更容易查找,不用关心绑定后的函数。)
Function.prototype.bind = function(context){
var self = this,
//缓存原型方法,提升性能。
slice = Array.prototype.slice,
//获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
args = slice.call(arguments, 1),
//返回函数
fBound = function(){
//把原型链指向要bind操作的函数,也就是原函数。
this.__proto__ = self.prototype;
self.apply(
//如果是new操作符实例出来对象,则为this。
//如果是直接执行方法,则为context
this instanceof self ? this : context,
//用bind时的参数合并执行时的参数,等于所有参数
args.concat(slice.call(arguments))
);
}
//bind后的方法是没有原型的,使其与浏览器原生表现一致
fBound.prototype = undefined;
return fBound;
}
//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
name: "hua",
age: 30
}
function foo(p1){
console.log(this.name); //undefined
console.log(Array.prototype.slice.call(arguments)); //[1, 2, 2, 3, 2]
console.log(p1) //1
}
var bFoo = foo.bind(obj, 1, 2);
console.log(bFoo.prototype); //undefined
var newBindFoo = new bFoo(2,3,2);
foo.prototype.say = function(){
console.log(this.name); //undefined
}
newBindFoo.say();
es6中的箭头函数(即没有prototype的方法)使得instanceof操作不再安全。
var fn = () => {}
fn.bind(null)() // Uncaught TypeError: Function has non-object prototype 'undefined' in instanceof check
class foo {
bar(){
console.log(this)
}
}
var nFoo = new foo();
nFoo.bar.bind(nFoo)() // Uncaught TypeError: Function has non-object prototype 'undefined' in instanceof check
五、优化最终版
Function.prototype.bind = Function.prototype.bind || function(context){
//只能对函数执行bind方法
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this,
//缓存原型方法,提升性能。
slice = Array.prototype.slice,
//获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
args = slice.call(arguments, 1),
//返回函数
fBound = function(){
//把原型链指向要bind操作的函数,也就是原函数。
//直接执行时this变得未知,所以加上try
try {
this.__proto__ = self.prototype;
} catch(e) {}
//怎么使用bind后的方法,new或者直接执行
var isNew = self.prototype ? this instanceof self : false;
self.apply(
//如果是new操作符实例出来对象,则为this。
//如果是直接执行方法,则为context
isNew ? this : context,
//用bind时的参数合并执行时的参数,等于所有参数
args.concat(slice.call(arguments))
);
}
//bind后的方法是没有原型的,使其与浏览器原生表现一致
fBound.prototype = undefined;
return fBound;
}