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