JS全解(六)this是啥,一次性讲清楚!

446 阅读4分钟

重要知识点:每节博客都会重复🙉🙉🙉

口诀🤨:四基两空一对象,还有五个falsy值。

七种数据类型

  • number string bool symbol
  • null undefined
  • object

五个 falsy 值

  • null undefined
  • 0 NaN
  • '' (空字符串)

你可能遇到过这样的 JS 面试题:

var obj = {
  foo:function(){
  console.log(this)
  }
}

var bar = obj.foo
obj.foo() // 打印出的 this 是 obj
bar() // 打印出的 this 是 window

请解释最后两行函数的值为什么不一样?

函数调用

首先要从函数的调用开始讲起。

JS(ES5)里面有三种函数调用形式:

  func(p1, p2) 
  obj.child.method(p1, p2)
  func.call(context, p1, p2) // 先不讲 apply

一般,初学者都知道前两种形式,而且认为前两种形式「优于」第三种形式。

从看到这篇文章起,你一定要记住,第三种调用形式,才是正常调用形式:

  func.call(context, p1, p2)

其他两种都是语法糖,可以等价地变为 call 形式:

  func(p1, p2) 等价于
  func.call(undefined, p1, p2)

  obj.child.method(p1, p2) 等价于
  obj.child.method.call(obj.child, p1, p2)

请记下来。(我们称此代码为「转换代码」,方便下文引用)

至此我们的函数调用只有一种形式:

func.call(context, p1, p2)

好了,我们开始讲this

规则一:如果不给任何的指向,this默认指向window

规则二:this是一个参数(个人意见)

假如没有this,不准用this

  1. 代码
  let person = {
    name: 'frank',
    sayHi(){
      console.log(`你好,我叫` + person.name)
    }
  }
  1. 分析 我们可以用直接保存了对象地址的变量获取 'name' 我们把这种办法简称为引用

问题一

  1. 代码
  let sayHi = function(){
    console.log(`你好,我叫` + person.name)
  }
  let person = {
    name: 'frank',
    'sayHi': sayHi
  }
  1. 分析 person 如果改名,sayHi 函数就挂了 sayHi 函数甚至有可能在另一个文件里面 所以我们不希望 sayHi 函数里出现 person 引用

问题二

  1. 代码
  class Person{
    constructor(name){
      this.name = name 
      // 这里的 this 是 new 强制指定的
    }
    sayHi(){
      console.log(???)
    }
  }
  1. 分析 这里只有类,还没创建对象,故不可能获取对象的引用 那么如何拿到对象的 name ?

需要一种办法拿到对象

这样才能获取对象的 name 属性

我们看看 Python 是怎么做的

  1. 代码
  class Person:
    def __init__(self, name): # 构造函数
      self.name = name

    def sayHi(self):
      print('Hi, I am ' + self.name)
  person = Person('xiaoming')
  person.sayHi()
  1. 特点
  • 每个函数都接受一个额外的 self
  • 这个 self 就是传进来的对象
  • 只不过 Python 会偷偷帮你传对象
  • person.sayHi() 等价于 person.sayHi(person)
  • person 就被传给 self

JS 没有模仿 Python 的思路

走了一条更难理解的路,难到我不得不先给大家讲 Python 的思路

JS 在每个函数里加了 this

  1. 用 this 获取那个对象
let person = {
  name: 'frank',
  sayHi(this){
    console.log(`你好,我叫` + this.name)
  }
}
  1. person.sayHi()相当于person.sayHi(person)

然后 person 被传给 this 了(person 是个地址)

这样,每个函数都能用 this 获取一个未知对象的引用了 3. 方便 sayHi 获取 person 对应的对象。person.sayHi() 会隐式地把 person 作为 this 传给 sayHi

总结一下目前的知识

  • 我们想让函数获取对象的引用
  • 但是并不想通过变量名做到
  • Python 通过额外的 self 参数做到
  • JS 通过额外的 this 做到: person.sayHi() 会把 person 自动传给 sayHi, sayHi 可以通过 this 引用 person

其他

  • 注意 person.sayHi person.sayHi() 的区别
  • 注意 person.sayHi() 的断句 (person.sayHi)( )

JS 怎么给出的两种调用

小白调用法

person.sayHi() 会自动把 person 传到函数里,作为 this

大师调用法

person.sayHi.call(person) 需要自己手动把 person 传到函数里,作为 this

应该学习哪种?

学习大师调用法,因为小白调用法你早就会了。

例一:

function add(x,y){
  return x+y
}

1.没有用到 this

add.call(undefined, 1,2) // 3

2.为什么要多写一个 undefined

  • 因为第一个参数要作为 this
  • 但是代码里没有用 this
  • 所以只能用 undefined 占位
  • 其实用 null 也可以

例二:

Array.prototype.forEach2 = function(fn){
  for(let i=0;i<this.length;i++){
    fn(this[i], i, this)
  }
}

1.this 是什么

由于大家使用 forEach2 的时候总是会用 arr.forEach2, 所以 arr 就被自动传给 forEach2

2.this 一定是数组吗

  • 不一定,比如 Array.prototype.forEach2.call({0:'a',1:'b'})

this 的两种使用方法

1.隐式传递

  • fn(1,2) 等价于 fn.call(undefined, 1, 2)
  • obj.child.fn(1)等价于 obj.child.fn.call(obj.child, 1)

2.显示传递

  • fn.call(undefined, 1,2)
  • fn.apply(undefined, [1,2])

箭头函数

JS 为了让新人不会this也能正常使用就出了箭头函数,但是需要注意:

1.里面的 this 就是外面的 this

console.log(this) // window
let fn = () => console.log(this) 
fn() // window

2.就算你加 call 都没有

fn.call({name:'frank'}) // window

有人说「箭头函数里面的 this 指向箭头函数外面的 this」,这很傻,因为箭头函数内外 this 就是同一个东西,并不存在什么指向不指向。

本文参考了方方:this 的值到底是什么?一次说清楚

由于本人水平有限,如有描述不准确的地方请给我留言,欢迎交流~