call的实现原理

Function.prototype:call/apply/bind 是Function原型上的方法,这三个方法都是用来改变函数中的THIS的

  • call[function].call([context],params1,params2,…)
    (1)[function]作为Function内置类的一个实例,可以基于__proto__找到Function.prototype的call方法,并且把找到的call方法执行;

    (2) 在call方法执行的时候,会把[function]执行,并且把函数中的THIS指向为[context],并且把params1,params2…等参数值分别传递给函数

  • apply[function].apply([context],[params1,params2,…])
    和call作用一样,只不过传递给函数的参数需要一数组的形式传递给apply

  • bind[function].bind([context],params1,params2,…)
    (1)语法上和call类似,但是作用和call/apply都不太一样;

    (2)call/apply都是把当前函数立即执行,并且改变函数中的this指向的,而bind是一个预处理的思想

    (3)基于bind只是预先把函数中的this指向[context],把params这些参数值预先存储起来,但是此时函数并没有被执行


下面我们来看一个例子

let body = document.body;
let obj = {
	name: "obj"
};

function func(x, y) {
	console.log(this, x, y);
}

  • func(10, 20); //=>THIS:WINDOW
  • obj.func(); //=>Uncaught TypeError: obj.func is not a function
  • call和apply的唯一区别在于传递参数的形式不一样
  • func.call(obj, 10, 20);// this:obj
  • func.apply(obj, [10, 20]);// this:obj
    call方法的第一个参数,如果不传递或者传递的是null/undefiend,在非严格模式下都是让this指向window(严格模式下传递的是谁,this就是谁,不传递this是undefined)
  • func.call(); // this:undefined
  • func.call(null); //this:undefined
  • func.call(undefined);//this:undefined
  • func.call(11);//this:Number {11}

body.οnclick=func和body.click=func()的区别

  • body.onclick = func; //=>把func函数本身绑定给body的click事件行为,此时func并没有执行,只有触发body的click事件,我们的方法才会执行

  • body.onclick = func(10, 20); //=>先把func执行,把方法执行的返回结果作为值绑定给body的click事件
    需求:把func函数绑定给body的click事件,要求当触发body的点击行为后,执行func,但是此时需要让func中的this变为obj,并且给func传递10,20

  • body.onclick = func.call(obj, 10, 20); //=>这样不行,因为还没点击func就已经执行了

  • body.onclick = func.bind(obj, 10, 20);

  • 在没有bind的情况下我们可以这样处理(bind不兼容IE6~8)
    body.onclick = function anonymous() {
    func.call(obj, 10, 20);
    };


bind原理和思想

// 执行BIND(BIND中的THIS是要操作的函数),返回一个匿名函数给事件绑定或者其它的内容,当事件触发的时候,首先执行的是匿名函数(此时匿名函数中的THIS和BIND中的THIS是没有关系的)
// BIND的内部机制就是利用闭包(柯理化函数编程思想)预先把需要执行的函数以及改变的THIS再以及后续需要给函数传递的参数信息等都保存到不释放的上下文中,后续使用的时候直接拿来用,这就是经典的预先存储的思想

Function.prototype.bind = function bind(context = window, ...params) {
	//this->func
	let _this = this;
	return function anonymous(...inners) {
		// _this.call(context, ...params);
		_this.apply(context, params.concat(inners));
	};
};
body.onclick = func.bind(obj, 10, 20); 

body.onclick = func.bind(obj, 10, 20); //将bind方法绑定给
原理:func.bind(obj, 10, 20)这个带了括号,表示执行bind的方法,即执行anonymous匿名函数,匿名函数中触发了 func.call(obj,10,20,ev)函数执行

body.onclick = function anonymous(ev) { //=>ev事件对象
func.call(obj,10,20,ev);
};

setTimeout(func.bind(obj), 1000);//将func.bind(obj)方法绑定给定时器
原理:1秒钟后执行匿名
setTimeout(function anonymous() {
}, 1000);


将类数组转成数组从而使用Array原型上的方法

// 我不是某个类的实例,不能直接用它原型上的方法,但是我可以让某个类原型上的方法执行,让方法中的THIS(一般是需要处理的实例)变为我,这样就相当于我在借用这个方法实现具体的功能 这种借用规则,利用的就是call改变this实现的,也是面向对象的一种深层次应用

// 需求:需要把类数组转换为数组
// 类数组:具备和数组类似的结构(索引和LENGTH以及具备INTERATOR可迭代性),但是并不是数组的实例(不能用数组原型上的方法),我们把这样的结构称为类数组结构

function func() {

// 1.Array.from

	let args = Array.from(arguments);
	console.log(args);

// 2.基于ES6的展开运算符

let args = [...arguments];
console.log(args);

// 3.手动循环

let args = [];
for (let i = 0; i < arguments.length; i++) {
	args.push(arguments[i]);
}
console.log(args);

// 4.ARGUMENTS具备和数组类似的结构,所以操作数组的一些代码(例如:循环)也同样适用于ARGUMENTS;如果我们让ARRAY原型上的内置方法执行,并且让方法中的THIS变为我们要操作的类数组,那么就相当于我们在“借用数组原型上的方法操作类数组”,让类数组也和数组一样可以调用这些方法实现具体的需求

 let args = Array.prototype.slice.call(arguments);
let args = [].slice.call(arguments);
 console.log(args);
 [].forEach.call(arguments, item => {
	console.log(item);
 });

}

// func(10, 20, 30, 40);

Array.prototype.slice Array原型上slice实现原理

 Array.prototype.slice = function slice() {
	// this->arr
	let args = [];
	for (let i = 0; i < this.length; i++) {
		args.push(this[i]);
	}
	return args;
};
let arr = [10, 20, 30];
console.log(arr.slice());

call方法的实现原理核心思想(成员访问)

// 核心原理:给CONTEXT设置一个属性(属性名尽可能保持唯一,避免我们自己设置的属性修改默认对象中的结构,例如可以基于Symbol实现,也可以创建一个时间戳名字),属性值一定是我们要执行的函数(也就是THIS,CALL中的THIS就是我们要操作的这个函数);接下来基于CONTEXT.XXX()成员访问执行方法,就可以把函数执行,并且改变里面的THIS(还可以把PARAMS中的信息传递给这个函数即可);都处理完了,别忘记把给CONTEXT设置的这个属性删除掉(人家之前没有你自己加,加完了我们需要把它删了)

// 如果CONTEXT是基本类型值,默认是不能设置属性的,此时我们需要把这个基本类型值修改为它对应的引用类型值(也就是构造函数的结果)
Function.prototype.call = function call(context, ...params) {
	//【非严格模式下】不传或者传递NULL/UNDEFINED都让THIS最后改变为WINDOW
context == undefined ? context = window : null;
	// CONTEXT不能是基本数据类型值,如果传递是值类型,我们需要把其变为对应类的对象类型
	if (!/^(object|function)$/.test(typeof context)) {
		if (/^(symbol|bigint)$/.test(typeof context)) {
			context = Object(context);
		} else {
			context = new context.constructor(context);
		}
	}
	let key = Symbol('KEY'),
		result;
	context[key] = this;
	result = context[key](...params);
	delete context[key];
	return result;
};
 let obj = {
	name: "obj"
};

function func(x, y) {
	console.log(this);
	return x + y;
}

console.log(func.call(obj, 10, 20)); 

思路:
// // 只要按照成员访问这种方式执行,就可以让FUNC中的THIS变为OBJ【前提OBJ中需要有FUNC这个属性】,当然属性名不一定是FUNC,只要属性值是这个函数即可
//obj.$$xxx = func;
//obj.$$xxx(10,20);

// 创建一个值的两种方法:对于引用数据类型来讲,两种方式没啥区别,但是对于值类型,字面量方式创建的是基本类型值,但是构造函数方式创造的是对象类型值;再但是,不管基本类型还是对象类型都是所属类的实例,都可以调用原型上的方法;(基本值无法给其设置属性,但是引用值是可以设置属性的)
// // 1.字面量创建
// let num1 = 10;
// let obj1 = {};
// new num1.constructor(num1);

// // 2.构造函数创建
// let num2 = new Number(10);
// let obj2 = new Object();

在这里插入图片描述

var name = 'Alibaba';
function A(x,y){
    var res=x+y;
    console.log(res,this.name);
}
function B(x,y){
    var res=x-y;
    console.log(res,this.name);
}
B.call(A,40,30);//10 "A"
B.call.call.call(A,20,10);//NaN undefined
Function.prototype.call(A,60,50);//不输出
Function.prototype.call.call.call(A,80,70);//NaN undefined

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值