JavaScript --- this 指向小结

无论是在java中还是在JavaScript中,this的指向一直是值得仔细思考的问题。最近做题也遇到不少相关的问题,所以在这里小小的总结下下,如有不正确,欢迎大家指正,感谢!!!希望我们一起学习进步,O(∩_∩)O哈哈~。

首先,与变量求值时候的词法作用域不同, this求值时候,是采用的动态作用域,总结起来,即this指向其调用者

具体地,可将this的调用可以分为下面五种情况:

  • 普通函数
  • 方法函数
  • 构造函数/class函数及其super
  • 通过bind、call、apply绑定this的函数
  • 箭头函数

1.  当在普通函数调用的时候,this指向widow

function fn () {
    function in(){
        console.log(this);
    }
    in(); // 内部this指向window
    }
fn();

分析:

其执行环境不在window

其调用者也不是window(非全局环境下作为默认函数调用是没有调用者的)。然而this却指向window, 这是不合理的。
JavaScript是一门在不断进化的语言,一开始的设计存在着诸多开放性问题,除了函数调用时this的指向问题,还有诸如重复声明,以及未声明便可以初始化的问题。
为了消除JS语法的一些不严谨、不安全之处,ECMAscript 5添加了第二种运行模式: 严格模式。
在严格模式下,此处this会合理的指向,console.log打印出来的将是undefined(因为没有调用者)。

'use strict'  // ----  严格模式下!!!
function test() {
// 指向undefined
console.log(this)
}
test()

2. 当方法调用的时候this指向调用对象

 注意: 这里的调用者是指执行时的直接调用者, 而不是声明时的储存位置 ! ! ! 如下:

var obj1 = {
   test: function () {
       console.log(this)
    }
}
obj1.test() // 这里 this 指向obj
const obj2 = {}
// 将obj1的test方法引用给obj2的 fn
obj2.fn = obj1.test
// 调用者为obj2, 故这里 this指向obj2
obj2.fn()

还有一种特殊情况,

(0, obj1.test)()  // this指向全局window

为什么会发生这种 this 丢失的现象呢, 其实(0, obj1.test)这个表达式的返回值即obj1.test指向的 -- > 源函数, 实际上进行的是普通函数调用, 而不是方法调用。其相当于:

fn = obj1.test>
fn()

3 . 构造函数调用指向实例对象

JavaScript中, 当使用new关键字对一个函数进行实例化时, 其函数会作为构造函数调用, 此时, 构造函数内部的this指向一个隐式创建的空对象, 最终若没有显式的返回对象的话, 则会默认返回这个隐式创建的对象。
new 的过程大致可以分为下面四个步骤:

  1. 隐式创建一个内部的空对象
  2. 将内部对象的原型与构造函数的原型链连接
  3. 执行构造函数内部代码, 此时this指向内部对象
  4. 若没有显式的返回对象时, 返回我们的内部对象。(若有显式的返回对象时, 则按普通函数返回)

其中,class属于es6的新特性,  与ES5中的构造函数没有本质上的区别, 其constructor及其中super的this指向依旧是我们的内部对象。


4. 通过bind、call、apply绑定this的函数

可以通过bindcallapply改变 this 的指向。
这是挂载在函数对象原型链上的三种方法(Function.prototype.bindFunction.prototype.callFunction.prototype.apply)。
其中callapply改变函数调用时的this指向,即将this 指向 其第一个参数,这两个方法的不同在于第二个参数的形式不同。apply()会以数组的形式作为【原函数】的参数调用, call() 会以普通参数的形式作为【原函数】的参数调用,举个栗子:

obj1.fn.call(obj2,[1,2,3,4])    

bj1.fn.apply(obj2,1,2,3,4)

这两种情况下, 1,2,3,4 作为函数fn的参数,this指向obj2。

call 的性能较 apply 要更好, 速度更快。参考:https://zhuanlan.zhihu.com/p/27659836

bind 只接受一个参数作为this的指向, 然后返回一个绑定了this的函数, 返回的函数除了this绑定了以外, 内部逻辑与原函数完全相同; bind方法没有副作用, 不会修改原函数。

当然, 部分浏览器上(举个栗子:Chrome) bind的性能开销较大,速度较慢, 有的框架会选择实现自己的bind, 如 Vue, 具体代码如下:

/**
 * Simple bind, faster than native
 * 为什么原生的bind慢,可以看(https://stackoverflow.com/questions/8656106/why-is-function-prototype-bind-slow)
 * 总结一下就是, 原生bind会判断一下`this instance of bound`如果是就实例化`bound`(不是目标函数, 而是bind自身)函数
 * **instance**的判断造成了性能损失(instance 是沿着原型链遍历查找的)
 */
export function bind (fn: Function, ctx: Object): Function {
  function boundFn (a) {
    const l: number = arguments.length
    /**
     * 在部分运行环境中call速度要比apply更快
     * 所以这里做了一个判断, 而不是全部直接都用apply
     */
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }
  // record original fn length
  /**
   * 记录原始函数的形参数量(原生的bind会绑定原始函数的形参数量)
   * eg: 
   *   function originFn (a, b, c) {} // 三个形参
   *   originFn.length // 3, 返回的是形参数量
   *   bind(originFn).length // 3, 返回的是上面原始函数的形参数量
   * 而这里boundFn.length会返回1, 即其自身的形参数量,
   * 故用_length来保留原始形参的数量, 以备以后可能用到
   */
  boundFn._length = fn.length
  return boundFn
}

5. 箭头函数

ES6中,通过箭头函数我们可以简单将函数的this指向绑定为外部函数的this, 举个栗子

var obj = {
  age: 18,
  changeAge: function (v) {
    const arrowFn = () => {
      this.age = 20
    }
    // 这里是作为普通函数调用, 其this理应指向window(非严格模式)或undefined(严格模式)
    // 但是因为其声明时候属于箭头函数, 其this指向外部函数的this
    // 即指向changeAge的this, 根据changeAge的调用方式而定
    arrowFn()
  }
}
// 此处changeAge的调用方式是方法调用, this指向调用者obj
// 故其内部的arrowFn函数的this也指向obj
obj.changeAge()
// 20
console.log(obj.age)

这里 this 的求值从动态作用域转换成了词法作用域, 不再是根据其直接的调用者而定, 而是指向其外部函数的调用者

var obj = {
  age: 18,
  changeAge: function (v) {
    var that = this
    function arrowFn () {
      // 修改that, 即外部this的age属性
      that.age = 20
    }
    arrowFn()
  }
}
// 此处是方法调用, this指向调用者obj
obj.changeCount()
console.log(obj.age)   // 20

所以对箭头函数使用applycallbind方法, 或改变其调形式为方法调用或函数调用, 都无法改变箭头函数其 内部 this 的指向。

var originObj = {
  number: 1,
  getArrowFn: function () {
    // 返回一个箭头函数, 此箭头函数调用时 返回this.number
    return () => this.number
  }
}
var obj = {
  number: 2
}
// 在这一步, 箭头函数arrowFn内部this的指向已经确定了
// 指向其外部函数getArrowFn的调用者, 即originObj
const arrowFn = originObj.getArrowFn()
// 通过call、apply、bind无法改变箭头函数的this
arrowFn.call(obj)    // 1
arrowFn.apply(obj)   // 1
arrowFn.bind(obj)()  // 1
// 通过函数调用或方法调用也无法改变箭头函数的this
arrowFn()   // 1
obj.fn = arrowFn   // 1
obj.fn()

最后  ,,  若通过 new 来将 箭头函数 当做一个构造函数进行实例化 会报错!!! 规范规定箭头函数是无法实例化的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值