JavaScript面试考点之this的理解
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)。this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。this在函数执行过程中,this一旦被确定了,就不可以再更改。
1、全局对象&调用普通函数
在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。如果普通函数是在全局环境中被调用,在非严格模式下,普通函数中 this 也指向全局对象;如果是在严格模式下,this 将会是 undefined。
使用严格模式后
2、作为对象方法的调用
在对象里的值,如果是原生值(primitive type;例如,字符串、数值、布尔值),我们会称之为「属性(property)」;如果对象里面的值是函数(function)的话,我们则会称之为「方法(method)」。如果函数作为对象的一个方法时,并且作为对象的一个方法被调用时,函数中的this指向直接调用函数的上一级对象。
3、作为构造函数调用
我们可以使用 new 关键字,通过构造函数生成一个实例对象。此时,this 便指向这个新对象。
1)使用new来调用Fn(..)时,会构造一个新对象并把它(obj)绑定到Fn(..)调用中的this。还有值得一提的是,如果构造函数返回了非引用类型(string,number,boolean,null,undefined),this 仍然指向实例化的新对象。
2)Fn()返回(return)的是一个对象(引用类型),this 会指向实例化的对象。
3)如果return的是一个非引用类型的值,this 会指向这个return的对象。
4、call、apply、bind方法可以改变this指向
它们的作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向。call、apply和bind是挂在Function对象上的三个方法,调用这三个方法的必须是一个函数。
1)call方法的第一个参数也是this的指向,后面传入的是一个参数列表。改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
2)apply方法接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。当第一个参数为null、undefined的时候,默认指向window。
使用 call 和 apply 时,如果给 this 传的不是对象,JavaScript 会使用相关构造函数将其转化为对象。比如传 number 类型,会进行new Number()操作,如传 string 类型,会进行new String()操作,如传 boolean 类型,会进行new Boolean()操作。
3)bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入),改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
调用fn(),则分别输出 Window 全局对象和1。
调用a(),分别输出Object {x: 2}和2,即this指向obj1。
然后调用b(),b相当于想再次改变a的指向,还是分别输出Object {x: 2}和2,即this指向obj1。即使是使用 call 或 apply 方法也不能改变这一事实,即永久的指向 bind 传入的第一次参数。
apply、call、bind三者的区别总结:
三者都可以改变函数的this对象指向
三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。bind是返回绑定this之后的函数,apply、call 则是立即执行。
4)手写call、apply、bind
a、call的实现原理
func函数基于__proto__找到Function.prototype.call,把call方法执行。在call方法内部「call执行的时候」 call(context->obj,...params->[10,20])。即把func中的this改为obj;并且把params接收的值当做实参传递给func函数;并且让func函数立即执行。
// func.call(obj, 10, 20);
手写代码:
b、apply的实现原理
类似于call。
c、bind的实现原理
func函数基于__proto__找到Function.prototype.bind,把bind方法执行。和call/apply的区别:并没有把func立即执行。在bind方法内部,把传递进来的obj/10/20等信息存储起来「闭包」;执行bind返回一个新的函数 例如:proxy,把proxy绑定给元素的事件,当事件触发执行的是返回的proxy,在proxy内部,再去把func执行,把this和值都改变为之前存储的那些内容
// document.body.addEventListener('click', func.bind(obj, 10, 20));
// document.body.addEventListener('click', proxy)
5、箭头函数
箭头函数没有自己的this绑定。箭头函数中使用的this,其实是直接包含它的那个函数或函数表达式中的this。
和普通函数不一样,箭头函数中的 this 指向了 obj,这是因为它从上一层的函数中继承了 this,它只会从自己的作用域链上找父级执行上下文的 this,你可以理解为箭头函数修正了 this 的指向。所以箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this。
箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。
同 bind 一样,箭头函数也很“顽固”,我们无法通过 call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略。
箭头函数是匿名函数,不能当成构造函数,不能使用new。
箭头函数没有原型属性prototype
箭头函数没有 arguments 对象
箭头函数和普通函数的区别
1)this指向:箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向,如call(),bind(),apply();普通函数的this指向调用它的那个对象。
2)参数:箭头函数不绑定arguments,取而代之用rest参数...解决。
3)普通函数可以有匿名函数,也可以有具体名函数;但是箭头函数都是匿名函数,不能作为构造函数,不能使用new。
4)箭头函数没有原型属性。
总结:
1)函数执行,看方法前面是否有“点”,没有“点”,this是window「严格模式下是undefined」,有“点”,“点”前面是谁this就是谁
2)给当前元素的某个事件行为绑定方法,当事件行为触发,方法中的this是当前元素本身「排除attachEvent」
3)构造函数体中的this是当前类的实例。
4)箭头函数中没有执行主体,所用到的this都是其所处上下文中的this。
5)可以基于Function.prototype上的call/apply/bind去改变this指向。
6、JQuery链式操作原理
链式操作原理:每次调用函数都返回调用函数的对象。之所以可以实现链式操作是因为其中的每个函数返回的都是jQuery对象本身。
链式调用的原理就是实例在调用内部方法的时候,返回当前调用这个方法的实例对象this。