手写:call,apply,bind

130 阅读2分钟

对比

1. call/apply

  1. 第一个参数就是改变的THIS指向,写谁就是谁(特殊:非严格模式下,传递null/undefined指向的也是window)
  2. 唯一区别:执行函数,传递的参数方式有区别,call是一个个的传递,apply是把需要传递的参数放到数组中整体传递
func.call([context],10,20) 
func.apply([context],[10,20])

2. bind

  1. call/apply都是改变this的同时直接把函数执行了,而bind不是立即执行函数,属于预先改变this和传递一些内容 =>"柯理化"
  2. 并且,绑定的时候可以额外传参数,执行的时候也可以额外传参数。

call

  1. 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 String、Number、Boolean
  2. 为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值
  3. 将函数作为传入的上下文(context)属性执行
  4. 函数执行完成后删除该属性
  5. 返回执行结果
function add(c, d) {
  return this.a + this.b + c + d;
}
const obj = { a: 1, b: 2 };

function es6call(context, ...args) {
	context = context || window // 1.
	context === null ? context = window : null;
	let type = typeof context;
	if (type !== "object" && type !== "function" && type !== "symbol") {
		//=>说明不是引用类型,是基本类型值
		switch (type) {
			case 'number':
				context = new Number(context);
				break;
			case 'string':
				context = new String(context);
				break;
			case 'boolean':
				context = new Boolean(context);
				break;
		}
	}
	let $fn = Symbol() // 2. 新建一个唯一的Symbol变量避免重复
	context.$fn = this;//  this = 需要执行的函数 add
	args = args ? args : []
	//以对象调用形式调用$fn,此时this指向context, 也就是传入的需要绑定的this指向
	let result = args.length > 0  ? context.$fn(...args) : context.$fn(); // 3. 执行context.$fn(),就是要执行的函数add,并且把参数传进来,此时 this指向context
	delete context.$fn; //4. 
	return result; 、、5. 
} 
		
console.log(add.es6call(obj, 3, 4)); // 10
  • 在function的原型上写方法也可以
Function.prototype.myCall = function((context, ...args){}

apply

  1. 前部分与call一样
  2. 第二个参数可以不传,但类型必须为数组或者类数组
function apply(context = window, args) {
        context.$fn = this;
        let result = context.$fn(...args);
        delete context.$fn;
        return result;
}

bind

  1. 需要考虑:
    • bind() 除了 this 外,还可传入多个参数;
    • bind 创建的新函数可能传入多个参数;
    • 新函数可能被当做构造函数调用;
    • 函数可能有返回值;
  2. 实现方法:
    • bind 方法不会立即执行,需要返回一个待执行的函数;(闭包)
    • 实现作用域绑定(apply)
    • 参数传递(apply 的数组传参)
    • 当作为构造函数的时候,进行原型继承
 ~ function anonymous(proto) {
    function bind(context){
                //=>this: 需要执行的函数
                //context may be null or undefined
                if (context == undefined) {
                        context = window;
                }
                // 获取传递的实参集合
                // arguments {0:context,1:10,2:20,length:3} 传进来的arguments是一个类数组.不是真正的数组,所以不能用数组的slice方法
                var args = [].slice.call(arguments,1) //相当于arguments.slice,获取传递的实参集合
                //需要最终执行的函数
                var  _this = this //obj.fn

                function anonymous(){ 
                        var innerArg = [].slice.call(arguments,0) // 接受执行anonymous的时候,传进来的参数。从0开始截。
                        args = args.concat(innerArg) // 拼接2个数组
                        // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作
                        var new_this = this instanceof _this ? this : context
                        _this.apply(new_this, args)
                }
         // 为了完成 new操作, 还需要做一件事情 执行原型 链接
                 // 维护原型关系
                var fnNoop = function(){} //空函数
                if(this.prototype){
                        fnNoop.prototype = this.prototype
                }
                anonymous.prototype = new fnNoop()
                return anonymous
    }
    proto.bind = bind


 }(Function.prototype)