在调用函数时,浏览器每次都会传递两个隐含的参数:1,函数的上下文对象this 2,封装传入实参的类数组对象arguments。
[].slice.call(arguments);//相当于[...arguments]的效果,两种写法都能将类数组转换为数组
//通过call()将[].slice中的this指向了arguments,继承数组上的slice方法,使其拥有了slice方法(类数组arguments本来是没有slice方法的。slice()如果不传参则是从第0项开始截取到length-1项并返回截取后的数组。
this
执行上下文中包含了变量环境和词法环境,此外还有outer和this,this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this。
this的设计缺陷?
详见:极客时间——从执行上下文的视角理解this
- 嵌套函数中的 this 不会继承外层函数的this
- 普通函数中的 this 默认指向全局对象 window
this的指向
this的指向是在函数调用时确认下来的(除了箭头函数),分为默认绑定、隐式绑定、显式绑定call apply bind、new绑定、箭头函数。
优先级:箭头函数>new构造函数绑定>显式绑定call apply bind>隐式绑定(对象形式调用)>默认绑定(window)
- 默认绑定:以函数形式调用(其实也相当于调用window的方法,如window.fun()),this是window,严格模式下,this绑定到undefined上并抛出错误;
- 隐式绑定:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的,谁调用this就是谁;
- 显式绑定:使用call和apply调用时,this是指定的那个对象;
- new构造函数绑定:以构造函数形式调用时,this是新创建的那个对象实例;
箭头函数
没有prototype属性,也不可以用new和arguments。
箭头函数不绑定this关键字,箭头函数中的this指向的是箭头函数定义位置的上下文this。
<script>
function Fn(){
console.log(this);
return ()=>{
console.log(this);
}
}
obj={'name':'zhangsan'}
ret=Fn.call(obj);//返回箭头函数
ret();//调用箭头函数
/*虽然在外面调用箭头函数,但是this不是window,而是箭头函数定义位置的上下文this。*/
</script>
<script>
var obj={
age:20,
say:()=>{
alert(this.age)
}
}
obj.say();//输出undefined。因为对象不能产生作用域,所以箭头函数被定义在全局作用域下,this指向window。
</script>
call&apply
都会调用函数执行:
- 当对函数调用call()和apply()时都会调用函数执行。(fun()=fun.call()=fun.apply())
- 在调用call()和apply()时可以将一个对象指定为第一个参数,此时这个对象将会成为函数执行时的this。第一个参数为undefined或null的时候,this会转变为window。
传参方式的不同:
- call()方法可以将实参在对象之后依次传递,如fun.call(obj,2,3);
- apply()方法需要将实参封装到一个数组中统一传递,如fun.apply(obj,[2,3]);
应用场景的不同:
- call()的主要应用是可以实现构造函数继承:
function Father(name,age){
this.name=name;
this.age=age;
}
function Son(name,age,score){
Father.call(this,name,age,score);//调用Father构造函数,并指定this指向Son的实例对象
this.score=100;
}
var son=new Son("ldh",18,100)
console.log(son)
- apply的主要应用是可以利用apply借助于数学内置对象求最大值,经常和数组有关系。
<script>
var arr=[1,3,6,9,0];
var max=Math.max.apply(Math,arr);
</script>
bind
bind()也可以改变函数内部this指向,但它不会调用函数,返回由指定的this值和初始化参数改造的原函数拷贝(返回改变this之后产生的新函数)。
另外注意,bind创建的新函数也可能传入多个参数且新函数可能会被当作构造函数调用。
如new Fun=fun.bind(obj,2,3)
主要应用:
如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用bind方法。
<body>
<button>点击</button>
<script>
//我们有一个按钮,当我们点击了之后,就禁用这个按钮,3s之后再开启这个按钮
var btn=document.querySelector('button')
btn.onclick=function(){
this.disabled=true;//这个this指向的是btn这个按钮
setTimeout(function(){
this.disabled=false;//定时器里面的this本来指向的是window对象,通过绑定btn
}.bind(this),3000)//这个this指向的是btn这个对象
}
</script>
</body>
手写call apply bind(实现原理):
手写call:
function test(){
console.log(this.name);
}
const obj={
name:'xiaohong',
}
test.call(obj);
//相当于给obj里添加test方法然后执行obj.test()
Function.prototype.mycall=function(obj){
console.log(this);
}
test.mycall(obj);
//ƒ test(){
// console.log(this.name);
//}
Function.prototype.myCall=function(obj){
const o=obj||window;//如果没传参默认是window
o.fn=this;//this指向函数,fn.call(obj) console.log(this)输出的是函数,所以就可以给对象添加方法,this就代表这个方法
let args=[...arguments].slice(1);//实参截取
let res=o.fn(...args);//执行o里的方法Fn
delete o.fn;//删除这个属性fn,不然函数fn的this就一直绑定给o这个对象了
return res;
}
手写apply:
Function.prototype.myApply=function(obj){
const o=obj||window;
o.fn=this;//将函数设为对象的属性
let res;
if(arguments[1]){//如果有参数
res = o.fn(...arguments[1]);
}else{
res=o.fn()
}
delete o.fn;
return res;
}
手写bind:
应用了函数柯里化。
bind和call、apply不一样的地方是bind会返回一个新的函数。fn.bind(obj,arguments)(arguments)
- 因为会返回新函数新函数调用时也可以进行传参,所以要进行参数的合并
- bind 创建的新函数作为普通函数调用时 this 指向所绑定的对象。但因为bind返回一个函数,所以这个函数可以作为构造函数加new创建一个实例对象,当作为构造函数通过 new去创建实例对象时bind指定的this对象就会失效了(因为new构造函数优先级>bind显式绑定),而是会指向创建的实例。因此需要将返回的函数进行具名化,然后判断有没有进行new操作
- 还有一点要注意:新创建的实例需要能够访问到原函数fn的原型对象,用到原型式继承
Function.prototype.myBind = function (obj) {
if(typeof obj!=='function'){
throw new TypeError('出错了');
}
let that = this;//和上面同理,this等同于函数fn,把这个函数存储一下。
let args = [...arguments].slice(1)//也可以用Array.prototype.slice.call(arguments,1)
let newfn = function() {
// args是之前使用bind绑定的参数,即第一个括号里传入的参数
// (当前花括号的代码块内的)arguments是在bind操作之后,其他的对象(比如window)调用返回的newfn时传入的参数。所以在调用newfn的时候,需要将bind·操作绑定的函数和后面自主传入的参数拼接起来,得到·finalArgs·
let finalArgs = args.concat([...arguments]);
//判断有没有使用new绑定
if (this instanceof newfn) {
//console.log(this instanceof newfn)返回true,说明此时this表示的不是fn函数了而是等同于new newfn创出来的实例。为true即发生了构造函数。
that.apply(this,finalArgs);
} else {
that.apply(obj, finalArgs);
}
}
// 因为·new·的操作中第二步为,obj.__proto__ = constructor.prototype
//constructor 就是这个 newfn
let o=function(){};//原型式继承 临时构造函数
o.prototype = that.prototype;
newfn.prototype = new o;
return newfn;
}