最近学JavaScript的时候被普通函数、箭头函数、构造函数这三个函数搞晕了,这里写一下学习笔记记录一下,防止遗忘 [笑哭]

普通函数和构造函数的区别

1. 创建变量和方法的方式不同

  • 普通函数里创建变量和方法
 1function fn(){
 2  let name = "John"
 3  let age = 18
 4  let say = function(){
 5    console.log(name,age,this)
 6  }
 7}
 8
 9
10function fn(){
11  let name = "John"
12  let age = 18
13  let say = () => {
14    console.log(name,age,this)
15  }
16}
  • 构造函数里创建变量和方法
 1function fn(name, age) {
 2  this.name = name;
 3  this.age = age;
 4  this.say = function () {
 5    console.log(this.name,this.age,this);
 6  }
 7}

2. 调用方式不同:

  • 普通函数的调用
 1function fn(){
 2  let name = "John"
 3  let age = 18
 4  let say = function(){
 5    console.log(name,age,this)
 6  }
 7  say()
 8}
 9fn() // "John"  18  window
10
11
12
13function fn(){
14  let name = "John"
15  let age = 18
16  let say = function(){
17    console.log(name,age,this)
18  }
19  return say
20}
21fn()() // "John"  18  window
  • 构造函数里函数的调用(先new出一个实例对象,再用.访问其属性/方法)
 1function fn(name, age) {
 2  this.name = name;
 3  this.age = age;
 4  this.say = function (){
 5    console.log(this.name,this.age,this);
 6  }
 7}
 8var f = new fn("John",18)
 9f.say() // "John"  18   fn {name: "John", age: 18, say: ƒ}

3. 作用不同:

构造函数的作用是用来新建实例对象的(我的理解是构造函数偏向更像一个Object)

值得注意的是:普通函数也可以 new 出来一个实例对象,但是由于没有对实例对象创建属性/方法(this.xxx = …),所以得到的实例对象是个空对象:

 1function fn(){
 2  let name = "John"
 3  let age = 18
 4  let say = function(){
 5    console.log(name,age,this)
 6  }
 7  }
 8var f = new fn
 9console.log(f) // {}

4. this 指向

构造函数的 this 指向由这个例子可以清楚的看到就是指向 new 创建出来的实例对象 f

 1function fn(name, age) {
 2  this.name = name;
 3  this.age = age;
 4  this.say = function (){
 5    console.log(this.name,this.age,this);
 6  }
 7}
 8var f = new fn("John",18)
 9f.say() // fn {name: "John", age: 18, say: ƒ}

普通函数的 this 指向就需要看是谁调用该函数

 1var name = "Jack"
 2var obj = {
 3  name: "John",
 4  say: function(){
 5    console.log(this.name,this)
 6  }
 7}
 8obj.say() // "John"  obj {name: "John", say: ƒ}
 9var s = obj.say
10s() // "Jack"  window

obj.say()s() 的区别在于前面谁在调用,obj.say()很明显是 obj 调用了 say() 方法,所以 this 指向 obj,而 s() 前面没有.默认就是 window 调用 say() 方法,所以 this 指向 window

普通函数和箭头函数的区别

1. 写法不同

 1var fn = function(){
 2  console.log("普通函数")
 3}
 4var fn = () =>{
 5  console.log("箭头函数")
 6}
 7fn() // 调用方式

2. 箭头函数是匿名函数,不能作为构造函数,不能使用new

 1var fn = () =>{
 2  console.log("箭头函数")
 3}
 4var f = new fn // 报错:Uncaught TypeError: fn is not a constructor

3. 箭头函数不绑定 arguments,取而代之用 rest 运算符 … 解决

  • arguments 属性:一个类数组的Arguments对象,里面包含了传递进来的所有实参。
  • rest 运算符语法: … args(args为随便起的变量名)
  • rest 运算符作用:把传递进来的实参信息,都以数组的形式保存到args变量中
 1var fn = function(){
 2  console.log(arguments)
 3}
 4fn(1,2,3,4,5) // Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
 5
 6var fn = () =>{
 7  console.log(arguments)
 8}
 9fn(1,2,3,4,5) // 报错:Uncaught ReferenceError: arguments is not defined
10
11var fn = (...a) =>{
12  console.log(a)
13}
14fn(1,2,3,4,5) // [1, 2, 3, 4, 5]

值得注意的是箭头函数里要是没有 rest 参数 … ,则只能拿到传入参数的第一个

 1var fn = (a) =>{
 2  console.log(a)
 3}
 4fn(1,2,3,4,5) // 1

4. this 指向

普通函数和箭头函数的最大区别是 this指向 :普通函数有自己的this,箭头函数里面没有this

上面提到普通函数的 this 指向是看谁调用它,this 就指向谁。

由于箭头函数没有this,所以我们定个规则来判断箭头函数的this指向:

  • 如果箭头函数被普通函数包含(注意不是obj),则this指向跟着最近一层普通函数指向;
  • 如果没有被普通函数包含,则箭头函数的this指向window 这里箭头函数的外层是普通函数 fn(),fn() 函数里面的 this 执行 obj,所以箭头函数的 this 跟着指向 obj
 1var obj = {
 2  fn: function(){
 3    console.log(this) // obj {fn: f}
 4    setTimeout(()=>{
 5      console.log(this) // obj {fn: f}
 6    },0)
 7  }
 8}
 9obj.fn()

箭头函数外面没有一层普通函数,所以会顺着指向最外面的 window

 1var obj = {
 2  fn: ()=>{
 3    console.log(this) // window
 4    setTimeout(()=>{
 5      console.log(this) // window
 6    },0)
 7  }
 8}
 9obj.fn()

如果把箭头函数里面改成普通函数写法,会怎样?

 1var obj = {
 2  fn: function (){
 3    console.log(this) // obj {fn: f}
 4    setTimeout(function(){
 5      console.log(this) // window
 6    },0)
 7  }
 8}
 9obj.fn()

这里值得注意的是:如果定时器里面是普通函数的写法,则定时器里面的 this 默认指向 window;但如果定时器里面是箭头函数的写法,则定时器里面的 this 就被修改了,不是默认值了,具体值的判断就按照箭头函数的 this 的判断规则

5. 箭头函数不能用 call 等方法修改里面的 this,因为里面就没有 this

 1var obj = {
 2  fn: () => {
 3    console.log(this)
 4  }
 5}
 6obj.fn.call(obj) // window

再看一个例子:

 1let obj = {
 2  a: 10,
 3  b: function(n){
 4    let f = (n) => n + this.a
 5    return f(n)
 6  },
 7  c: function(n){
 8    let f = (n) => n + this.a
 9    let m = {
10      a: 20
11    }
12    return f.call(m,n)
13  }
14}
15console.log(obj.b(1)) // 11
16console.log(obj.c(1)) // 11,而不是 21

f(n) 的结果是 11 可以理解,f.call(m,n)不是应该让 f 里的 this 指向对象 m 了吗,很明显,结果传入的参数只有 n,所以再次证明箭头函数不能用 call 等方法修改里面的 this 的结论

 1var a = 1
 2function F(){
 3  this.a = 2
 4  return ()=>{
 5    console.log(this.a)
 6  }
 7}
 8var f = new F()
 9f() (这里是调用了f实例里的箭头函数// 2

归纳一下 this 指向的判断

  1. 普通函数的 this 指向是看谁调用它,this 就指向谁,跟在哪里调用没有关系
  2. 箭头函数没有this,判断规则是:如果箭头函数被普通函数包含,则this指向跟着最近一层普通函数指向;如果没有被普通函数包含,则箭头函数的 this 则指向 window
  3. 构造函数的 this 指向该构造函数创建出来的实例对象
  4. setTimeout里面如果是普通函数则 this 指向 window;如果里面是箭头函数,则 this 的规则按照箭头函数 this 指向判断规则

关于优先级及如何综合判断,参考vJS 中 this 指向问题 这篇文章,有个口诀可以看一下:

箭头函数、new、bind、apply 和 call、obj. 、直接调用、不在函数里

  • 看到箭头函数,其他都不用看了,按照箭头函数的 this 指向判断规则就行
  • new bind()返回的函数
 1function func() {
 2  console.log(this, this.__proto__ === func.prototype)
 3}
 4
 5boundFunc = func.bind(1) // func() { console.log(this, this.__proto__ === func.prototype) }
 6var b = new boundFunc
 7console.log(b) // func {} true

利用 new 关键字对 bind() 方法返回的函数创建实例对象,可以看到 this 指向创建的实例对象了,而不是Number {1}

  • bind()返回的函数.apply()
 1function func() {
 2  console.log(this)
 3}
 4
 5boundFunc = func.bind(1)
 6boundFunc.apply(2) // Number {1}

bind() 返回的函数使用 apply() 改变 this 指向,结果 this 指向仍然是 Number {1},而不是 Number {2}

改变 this 的指向

1. call() / apply() / bind() 方法

  • 函数.call(对象, 参数1, 参数2, 参数3,…)
  • 函数.apply(对象, 参数数组)
  • 函数.bind(对象, 参数1, 参数2, 参数3,…)

理解:fn.call/apply/bind(obj,…) 就是调用 fn 函数,fn 函数里面有 this 关键字的话,直接把 this 换成是 obj 即可

场景:

 1var p1 = {
 2  name : "小王",
 3  gender : "男",
 4  age : 24,
 5  say : function() {
 6    console.log(this.name + " , " + this.gender + " ,今年" + this.age); 
 7  }
 8}
 9var p2 = {
10  name : "小红",
11  gender : "女",
12  age : 18
13}
14p1.say(); // "小王,男,今年24"

如何使用 p1 对象里的 say() 方法输出 p2 对象的数据呢?

 1p1.say.call(p2)
 2p1.say.apply(p2)
 3p1.say.bind(p2)()

这里说一下三种方法的区别:

1.1 bind() 区别于 call() 和 apply()

call 和 apply 都是对函数的直接调用,而 bind 方法返回的仍然是一个函数,因此后面还需要 () 来进行调用才可以。

 1p1.say.call(p2) // "小红 , 女 ,今年18"
 2p1.say.apply(p2) // "小红 , 女 ,今年18"
 3p1.say.bind(p2) // ƒ () { console.log(this.name + " , " + this.gender + " ,今年" + this.age);  }
 4p1.say.bind(p2)() // "小红 , 女 ,今年18"
1.2 apply() 区别于 call() 和 bind()——传参

如果指定函数的 this 指向后还要传入参数到函数里面,就直接在第一个参数后面加上去!!

apply() 传入参数相比于其他两种方法是:用一整个数组来代替一个一个的参数传入

 1function greet (person1, person2, person3) {
 2  console.log(`Hello, my name is ${this.name} and I know ${person1}, ${person2}, and ${person3}`)
 3}
 4const user = {
 5  name: 'Tyler',
 6  age: 27,
 7}
 8const people = ['John', 'Ruby', 'Deck']
 9
10greet.call(user,people[0],people[1],people[2])
11// Hello, my name is Tyler and I know John, Ruby, and Deck
12
13greet.apply(user,people)
14// Hello, my name is Tyler and I know John, Ruby, and Deck

2. 把this用一个变量存起来

这里原本setTimeout里面的普通函数的this会指向window,而我们需要其指向F()

 1var num = 0;
 2function F (){
 3  var that = this;    //将this存为一个变量,此时的this指向f
 4  this.num = 1,
 5    this.getNum = function(){
 6    console.log(this.num);
 7  },
 8    this.getNumLater = function(){
 9    setTimeout(function(){
10      console.log(that.num);    //利用闭包访问that,that是一个指向obj的指针
11    }, 1000)
12  }
13}
14var f = new F(); 
15f.getNum(); // 1  打印的是obj.num,值为 1
16f.getNumLater(); // 1  打印的是obj.num,值为 1

归纳一下函数的调用方式

1. 函数名方式(在函数里面调用)

 1function fn(){
 2  let say = function(){
 3    console.log(this)
 4  }
 5  say()
 6}
 7fn() // window
 8
 9function fn() {
10  var say = () => {
11    console.log(this);
12  }
13  say()
14}
15fn(); // window

这种调用方式必须得有函数名称,其余三种可以没有函数名称,第二种可以有也可以没有

2. return 方式(闭包)(在函数外面调用)

 1function fn(){
 2  let say = function(
 3
 4  ){
 5    console.log(this)
 6  }
 7  return say
 8}
 9fn()() // window
10
11function fn(){
12  return function(){
13    console.log(this)
14  }
15}
16fn()() // window
17
18function fn() {
19  return () => {
20    console.log(this);
21  }
22}
23fn()(); // window

3. 自执行方式

 1(function(形参){
 2    console.log(形参)
 3})(实参)
 4
 5((形参)=>{
 6    console.log(形参)
 7})(实参)

4. setTimeout()方式(有点像自执行)

 1setTimeout(function(){
 2  console.log(this)
 3},0)
 4
 5setTimeout(()=>{
 6  console.log(this)
 7},0)

总结

  1. 构造函数是普通函数中的一种,特点是可以用来构造实例对象继承属性和方法
  2. this指向无非就三种:window、构造函数创建的实例对象、call/apply/bind方法里面第一个参数
  3. 普通函数的 this 指向由谁调用来判断,谁调用 this 就指向谁
  4. 构造函数的 this 指向由其创建出来的实例对象
  5. 箭头函数的 this 跟着最近一层的普通函数的this指向,要是没有被任何普通函数包裹,则会指向 window
  6. 改变 this 指向的有两种方案:call/apply/bind 和 var that = this

参考文章

个人笔记记录 2021 ~ 2025