call & apply & bind
前言
原型&原型链,函数的3种角色:
普通函数:闭包和作用域
构造函数:类。原型和原型链,类和对象有关
对象
每个函数都是Function这个类的实例,那每个函数都可以调用Function.prototype原型上的方法
// 一道闭包的开胃题
function fun(n,o){
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var c=fun(0).fun(1);
c.fun(2);
c.fun(3);
this问题
-
给元素绑定事件, 那么事件回调中的this就是dom元素本身
-
普通函数执行,前面有点的,点前面是谁执行主体就是谁,this就是那个执行体,没有则是window(严格模式下是undefined)。自执行函数中的this默认是window
-
构造函数中的this就是创建的那个实例
原型上常用的方法:call apply bind
每个函数都可以调用这3个方法,这3个方法都是用来改变this指向的
我们从 实例.方法这个角度去看call
fn.call(); 这里执行的是call方法(此时你还不知道call是干什么用的,哈哈)
这部分的理解很重要
call
第一个参数为this的值,不传为window(严格模式下为undefined)
window.name='WINDOW'
let obj={
name:'OBJ',
}
function fn(){
console.log(this.name);
}
fn();
fn.call(obj);
obj.fn=fn;
obj.fn.call();
语法:
call函数执行做了以下几件事:
-
让函数中的this变成context,把后面的每一项参数未来传给函数
-
会让这个函数执行
this的第4种情况:基于call改变
Object.prototype.toString()
Object.prototype.toString.call(100)
原理实现
不固定个数的实参,怎么在函数中接收?arguments。内置实参集合(类数组)。
function call(context,...arg){
let result=null;
console.log(arg);
// 不传默认为window
context = context || window;
context.$fn=this;
return context.$fn(...arg);
delete context.$fn;
return result;
}
Function.prototype.call=call;
let obj={
name:'OBJ',
}
function sum(){
console.log(this);
}
sum.call(obj,10,20,30);
这里的实现是有缺陷的:
但是通过这个锻炼出来的能力:
怎么重编写一个方法,能够基于面向对象、基于原型链自己扩展方法,怎么来实现自己的功能
充分理解call的语法
阿里面试题
function fn1(){ console.log(1) }
function fn2(){ console.log(2) }
let obj={}
fn1.call(fn2);
fn1.call.call(fn2);
// fn2.fn1();
Function.prototype.call(fn1);
// fn1.call(obj);
Function.prototype.call.call(fn1);
解题思路:
首先第一题 fn1.call(fn2);
fn1.call(fn2);
-
首先call执行
-
按源码,context是fn2,this是fn1,相当于给fn2加上$fn属性,值为fn1
-
然后再执行fn2.$fn,即fn1
从代码和语义都是fn1执行
第二题 fn1.call.call(fn2);
fn1.call.call(fn2);
永远都从最后一个 .call 开始算
fn1.call.call(fn2); 前面画一条中划线,上面写个call
-
执行的是最后一个call方法
-
xxx
第三题 Function.prototype.call(fn1);
Function.prototype.call(fn1);
Function.prototype是一个匿名空函数
总结:
1个call,让点号左边的执行;
n个call,让括号里面的执行;
js面试题:fn.call.call.call.call(fn2) 解析_嘿嘿-CSDN博客
apply & bind
apply
let obj={
name:'OBJ'
}
function fn(n,m){
console.log(this.name);
console.log(n+m);
}
fn.call(obj,10,20);
fn.apply(obj,[10,20]);
bind
-
先执行一个匿名函数
-
在执行匿名函数的时候去执行fn,相当于点击的时候执行了2个函数,先执行匿名函数;再执行fn。而执行fn的时候再去改变this
用bind
把fn中的this预先改变为obj,但是fn并没有执行
这种“预先做啥事情”的思想叫做“柯理化函数”
let obj={
name:'OBJ'
}
function fn(n,m){
console.log(this.name);
}
window.document.body.onclick=fn.bind(obj);
获取数组中的最大值和最小值
-
先排序
-
Math.max/min
传的时候是数组,但是相当于一个个传 => apply
-
假设法
let arr=[23,45,12,4,5,6,98,109];
// arr.sort((a,b)=> a-b);
let max=arr[0];
for(let i=1;i<arr.length;i++){
if(arr[i]>max) max=arr[i];
}
console.log(max);
*柯理化
预先把fn中的this处理成obj,
-
用bind是完全可以搞定的。把预先给fn的参数已经有了,fn中的this也改为了obj
-
自己先绑一个匿名函数
bind原理:
-
myBind方法执行形成一个闭包,把实参信息分别用私有变量存储起来,返回的函数被以外的占用了;
-
返回的函数真正执行的时候,很多变量都是往上级作用域闭包里面找;
就相当于,myBind执行形成闭包,把一些信息预先存储起来,当以后返回的小函数用到这些信息的时候,直接从闭包里面拿来用。
总结柯理化:
形成闭包存点东西,以后能够供里面的小函数使用,供里面的子集作用域使用
let obj={
name:'OBJ'
}
function fn(...arg){
console.log(this);
console.log(arg);
}
function clickFn(...arg){
fn.call(obj,100,200,...arg);
}
// document.body.onclick=clickFn;
// document.body.onclick=fn.bind(obj,100,200);
Function.prototype.myBind=function(context,...arg){
let _this=this;
return function(...arg1){
_this.call(context,...arg,...arg1)
}
}
document.body.onclick=fn.myBind(obj,100,200);
题目
面试题
柯里化的递归嵌套,闭包套闭包
fn.bind(null, ...arg) 用bind把之前的实参预先存起来
解题代码:
function add(n1,n2,n3){
return n1+n2+n3;
}
function currying(fn,length){
length = length || fn.length;
return function(...args){
if(args.length>=length){
return fn(...args)
}
return currying(fn.bind(null,...args),length-args.length);
}
}
add = currying(add,3);
console.log(add(1,2,3));
console.log(add(1)(2)(3));
console.log(add(1,2)(3));
function fn(a1,a2,a3){
return a1+a2+a3;
}
let f1=fn.bind(null,1)
let f2=f1.bind(null,2)
console.log('f2')
// 通过bind把之前的实参都保存起来
console.log(f2(3));
console.log(f1(2));