JavaScript this

JavaScript 中的 this

我终于能够写这章东西了。拖这么久的缘由是由于我在写业务代码,加上毕设的代码结构设计搞得我有点懵逼,加上以前确实有点懈怠,而后去写了个简易版的 MVVM 双向绑定和指令渲染的东西,通过那个小项目我发现我对 内存变量闭包this 有了更近一步的理解,而后加上看了点书有了点本身的理解,因而今天记录一下。git

我所写的这些东西只是我本身的理解,不表明就是绝对正确的,是我对 JavaScript 的见解和理解。github

我要讲的很杂很乱,由于这节原本就很庞大,涉及到了好多东西,我准备从:变量与内存闭包与 this绑定 this 这几个方面讲如下我本身的见解。缓存

变量、内存

这个其实很简单,咱们在代码之中写下了一堆又一堆的变量:bash

let foo = 'test'
const fn = function () {
  let name = 'Nicholas'
  console.log(name)
}
let handler = {
  addHandler: function (name, handler) {},
  fire: function () {}
}
复制代码

以上咱们定义了: 普通变量函数对象变量。这三种大概就是咱们平时打代码过程当中定义的最多的东西了。首先要肯定的是,基本数据类型是直接引用,而对象(引用变量)则被存储在了内存中,咱们没法直接访问到内存,只能取得对象的引用。 这句话意思是:变量或者说对象它就安静地在内存里(若是没被垃圾收集机制清除),而咱们定义地变量名只是对其引用。因而:闭包

let fns = {
  sayHi: function () {
    console.log('Hi')
  },
  fight: function () {
    console.log(`I'll fight with you`) } } let ffight = fns.fight 复制代码

上述代码中,我本身认为:首先确定是有两个函数在内存中躺着,而后 fns.sayHi 指向了其中一个函数,fns.fight指向了另外一个函数,而 ffight 指向的函数和 fns.sayHi 指向的是同一个函数,它们都只是对内存中存在的对象进行了引用 因此,我认为:内存和变量实际上较为独立又紧密联系:变量自己存储在这里,它只是指向了内存中的某一个对象而已,并没有它用。app

闭包与this

在我自认为理清楚了变量与内存的关系以后,我彷佛对闭包与this也有了一点认识。首先上一节闭包中我其实对闭包是什么已经有了认知,可是那时候对this这个东西有点模糊,只是依稀知道 在理解this时不可用闭包的词法做用域来认知。 首先看一段代码:函数

let fn = function () {
  let name = 'Nicholas'
  return function () {
    console.log(name)
  }
}

function getTimeout () {
  setTimeout(fn(), 1000)
}

getTimeOut() // 1秒后输出 Nicholas
复制代码

上面这段代码的结果应该十分简单,由于 fn() 获得的函数保留了对 fn 做用域中的 name 的引用,因此依然能够访问到 name。 我本身的理解:最开始我认为 this 就是对当前的引用,至于当前是什么,我相信不少人都和我一开始同样认为 所谓当前就是这行代码执行时它能访问到的变量环境,因而就有了下面这段代码:学习

var name = 'Bob'

function getTimeout () {
  setTimeout(function () {
    console.log(this.name)
  }, 1000)
}

var obj = {
  name: 'Nicholas',
  getTimeout: getTimeout
}

obj.getTimeout() // 1秒后输出 Bob
复制代码

这里我以为应该先上结论:this指向调用它的执行环境,毫不能用词法做用域的方法去找this的指向ui

若是照着咱们最开始的理解,当执行这个函数的时候要去访问 name,咱们天然而然会顺着代码书写位置去看,咱们在 obj 函数中找到了name,因而咱们觉得会this就是obj,应该输出 Nicholas。 可是这里咱们在看看上面这段代码之中的变量和内存:首先咱们定义了一个函数 getTimeout,它在内存中,在 setTimeout 的参数中咱们其实又定义了一个匿名函数,它也在内存中,并且obj.getTimeout就是那个匿名函数。而后咱们调用了getTimeout, 这个函数中咱们使用定时器去调用那个匿名函数,因而1秒后,至关于执行了这个函数,这个函数就是普普统统的执行了:this

// 像这样执行了:
// 这里咱们先取得这个匿名函数的引用,虽然咱们是从新定义了一个新的函数,为了演示做用,这里咱们假设 noName 就是那个匿名函数
function noName () {
  console.log(this.name)
}
// 1秒后咱们调用这个匿名函数
noName()
复制代码

这样看,结果就比较明显了,这里咱们天然能够去看 name 就是全局的 Bob。 而咱们最多见的作法是缓存this:

var name = 'Bob'

function getTimeout () {
  var that = this
  setTimeout(function () {
    console.log(that.name)
  }, 1000)
}

var obj = {
  name: 'Nicholas',
  getTimeout: getTimeout
}

obj.getTimeout() // 1秒后输出 Nicholas
复制代码

咱们平时就是用这种绑定的方式获得了咱们想要的结果,咱们理一下思路:

咱们知道this的绑定是动态的即它在执行前才能肯定指向,就是由于多个变量能够指向同一个变量才有这种说法,obj.getTimeout() 这行代码执行了内存中的代码,可是执行这串代码的环境确实 obj,因而这时候 getTimeout 这个函数中的 this 天然就是 obj

紧接着咱们缓存了 thisthat 变量中,而后1秒以后咱们调用了匿名函数,该函数是个闭包,它拥有了 getTimeout 函数的变量对象的访问权限,天然就能够访问到 that 变量,因而这个 that 就指向了 obj,obj.name天然就是 Nicholas了。

因而咱们获得了那个结论:函数中的this,实际上是在运行时决定的,虽然咱们运行的是同一段代码,可是调用这段代码的方式是不同的,就为这段代码中的this绑定了不一样的值。

绑定this

咱们须要绑定this,才能获得咱们想要的结果。

  • 隐式绑定:obj.fn(),这个时候咱们经过 obj 调用 fn,其中this天然被 obj 绑定了
  • 硬绑定:fn.call(obj, args)或者fn.apply(obj, args),这样咱们其实至关于传入一个对象使它做为 fn 函数运行时的this。
  • bind函数:bind 是每一个 Function 对象都有的方法,其返回结果是一个函数,使用方法:let foo = bar.bind(obj),这个时候只是将this的指向绑定成obj。
  • new操做符:let bar = new Fn(),咱们在第六章讲过 new发生过什么,因此这边的this指向就是返回的对象。

既然有多种方式,天然分出优先级:

  1. new
  2. bind
  3. 硬绑定
  4. 隐式绑定

证实:

// 咱们定义一个函数
var name = 'Bob'
var obj1 = {
  name: 'Nicholas',
  test: test
}
var obj2 = {
  name: 'Peter',
}
var obj3 = {
  name: 'Shally'
}
function test () {
  console.log(this.name)
}
// 隐式绑定和硬绑定:
obj1.test() // Nicholas
obj1.test.call(obj2) // Peter
// bind和硬绑定
let fn = test.bind(obj3)
fn() // Shally
fn.call(obj2) // Shally
// new和bind 我以为这两个其实没什么比较,由于new出来是个对象,这个对象就是this,具体理解仍是看代码吧
function Person (name) {
  this.name = name
}
// 首先咱们试图建立另外一个构造函数,让这个构造函数的this经过bind绑定一个对象
// 所以咱们须要一个空对象
var testObj = {}
// 接着咱们建立另外一个构造函数
var AnotherFn = Person.bind(testObj)
// 紧接着咱们调用这个AnotherFn
AnotherFn('Nicholas')
console.log(testObj.name) // Nicholas
// 接着咱们经过另外一个构造函数 new 一个对象出来
var person = new AnotherFn('Bob')
// 这个结果其实说明,new 这个操做符其实饼不是修改一个对象,而是先建立这个对象,而后this就是这个对象,而后返回这个对象
// 若是bind优先级更高,则这时候 testObj.name 应该是 Bob,而实际状况像咱们说明并不是如此。
console.log(testObj.name) // Nicholas
console.log(person.name) // Bob
复制代码

以上就是几种绑定this的状况。

结语

写这一节真的是要死要活。由于其实到写以前我都不是很清楚this的语法,只是模棱两可,如今有些理解,就赶忙记录一下。可是我以为我如今的理解应该也是瑕疵挺多的吧。一步一步来,慢慢学习。

应该会挑个时间,写一下前两天写的 MVVM 的那个思路,感受仍是有收获的,虽然别人以为很简单,可是本身写起来仍是印象深入些。

持续更新在github

相关文章
相关标签/搜索