call apply bind 箭头函数 的实现原理
(一)call 源码
call() 和 apply() 类似于借用,相当于 A 对象内部有了 B 对象的方法,实质是函数在运行时指定 this 值,打破了解析器在函数调用时创建执行环境时this绑定的规则。
例子:
const val = {
a:1
}
function add(b,c){
return this.a + b + c
}
console.log(add.call(val,2,3)) //6
相当于:
1.给 val 对象增加方法 add
2.执行 val 对象的方法 add
3.删除 add 方法
val.add = function(b,c){...}
console.log(val.add(2,3)) //6
delete val.add
call 原生实现:
Function.prototype.callFunc = function(context){
var context = context || window //undefined 时 this 绑定在全局
context.fn = this
var args = []
for(var i = 1, len = arguments.length; i < len; i++){
args.push('arguments[' + i + ']') //i 为1,除去第一个参数
}
var result = eval('context.fn(' + args + ')') //eval 执行字符串内容
delete context.fn
return result
}
通过在第一个参数 context 的属性中存储了调用 callFunc 函数的方法,然后再在 context 的执行环境中调用该属性(即执行该方法)
es6 的 rest 写法:
Function.prototype.callFunc = function(context){
var context = context || window //undefined 时 this 绑定在全局
context.fn = this
var args = []
for(var i = 1, len = arguments.length; i < len; i++){
args.push(arguments[i]) //i 为1,除去第一个参数
}
var result = context.fn(...args)
delete context.fn
return result
}
(二)apply 源码
和 call 发生的过程一样,不过需要判断第二个参数是否是数组
apply 原生实现:
Function.prototype.applyFn = function(context,arr){
var context = context || window
context.fn = this
var result
if(!arr){ //不输入第二个参数时
result = context.fn();
}else{
if(!(arr instanceof Array))
throw new Error('params must be array');
else
result = eval('context.fn('+arr+')');
// es6 的 rest 写法: var result = context.fn(...args)
}
delete context.fn
return result
}
(三)bind 源码
bind 和 call、apply 表现有很大差别
区别:
(1)bind() 方法会创建一个新函数。 当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一系列参数将会在传递的实参后传入作为它的参数。
例子:
function test(c,d){
this.a = 1
console.log(this.a + this.b + c + d)
}
var func = test.bind({b: 2}, 3);
func(4) //1+2+3+4,输出为10
由此可知与 call、apply 不同,该方法返回的是一个函数而不是执行结果
(2)通过 new 的方式创建这个绑定对象的实例,该实例的 bind() 函数对于 this 的绑定将失效,但是传递的参数仍保留
function test(c,d){
this.a = 1
console.log(this.a + this.b + c + d)
}
var func = test.bind({b: 2}, 3);
new func(4) //输出 NaN,因为该实例内 this.b 为 undefined
func(4) //1+2+3+4,输出为10
bind 源码:
Function.prototype.bind = function(context){
if (typeof this !== "function")
throw new TypeError('what is trying to be bound is not callback');
var _this = this //保存原函数的执行环境
var args = Array.prototype.slice.call(arguments,1) //保存之前传入参数
var sub = function(){
//把之前传入的参数与之后传入的参数合并
/注意这里的 arguments 与 函数外的 arguments 不是同一个
var argsBind = args.concat(Array.prototype.slice.call(arguments))
//判断函数是作为构造函数还是普通函数
//如果是构造函数,返回 true ,把 this 绑定在该函数实例
//如果是普通函数,则 this 继续绑定在 context 中
var env = this instanceof Super ? this : context;
return _this.apply(env,argsBind )
}
function Super(){}
Super.prototype = this.prototype;
sub.prototype = new Super()
//上面三句话相当于 sub.prototype = Object.create(this.prototyoe)
return sub //返回函数
}
1.最开始通过 args 保存之前传入的参数实现闭包,而后使用返回函数的 arguments (后来传入的参数)和 args 拼接,实现了传入参数的保存
2.通过判断返回函数 sub 中的 this 是否 Super 的实例来判断函数是作为构造函数还是普通函数,因为如果是构造函数,new 实例化后这个 this 指向 sub 的实例,而 sub 继承 Super ,所以该实例是 Super 的实例;如果是普通函数,则这个 this 指向 sub 这个构造函数本身,不是 Super 的实例
(四)箭头函数
直接上箭头函数转化成 ES5 例子
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
由此可知,箭头函数里面根本没有自己的this,而是引用外层的this。