call apply bind 箭头函数 的实现原理

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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章