Simonzhangs' blog Simonzhangs' blog
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • JS设计模式总结
  • 《Vue》
  • 《React》
  • 《TypeScript 从零实现 axios》
  • TypeScript
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • apple music
  • extension
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Simonzhangs

前端学习探索者
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • JS设计模式总结
  • 《Vue》
  • 《React》
  • 《TypeScript 从零实现 axios》
  • TypeScript
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • apple music
  • extension
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JS基础

    • typeof与instanceof
    • JS变量声明的六种方式
    • this指向
      • 改变 this 的指向
        • 箭头函数
        • 函数内部使用_this = this
        • apply、call、bind
      • JS中函数调用
        • 作为一个函数调用
        • 函数作为方法调用
        • 使用构造函数调用函数
        • 作为函数方法调用
    • ==和===运算符区别
    • Promise基础
    • JS异步发展
  • 基础
  • 内置对象
  • 面向对象
  • 异步操作
  • DOM
  • 事件
  • 浏览器模型
  • JS常见高级函数
  • 《JavaScript教程》笔记
  • JS基础
simonzhangs
2022-05-09
目录

this指向

this 永远指向最后调用它的那个对象。虽然 JS 是静态作用域,但是 this 的执行是函数执行时候确认的,它是需要根据上下文的进行判断的。同一个函数在不同地方被调用,结果可能会不一样,因为函数也是一个对象,可以赋值给另外一个变量进行调用,它本质上可以当作一个对象,只不过是在执行过程值的指向会不同。

var name = "windowsName";
function a() {
  var name = "Cherry";

  console.log(this.name); // windowsName

  console.log("inner:" + this); // inner: Window
}
a();
console.log("outer:" + this); // outer: Window
1
2
3
4
5
6
7
8
9
10

a 调用的地方是 a(),前面没有调用的对象那么就是全局 window 对象,故 this 指向 window。

var name = "windowsName";
var a = {
  name: "Cherry",
  fn: function () {
    console.log(this.name); // Cherry
  },
};
a.fn();
1
2
3
4
5
6
7
8

此时是 a 调用的 fn(),故 fn 中 this 指向 a。但是如果把 fn 改为匿名函数,则打印的是 windowsName,因为匿名函数执行的是最近一层非箭头函数的 this,即 window:

var name = "windowsName";
var a = {
  name: "Cherry",
  fn: () => {
    console.log(this.name); // windowsName
  },
};
a.fn();
1
2
3
4
5
6
7
8

如果改成这样子,则会输出 undefined,因为此时 fn 中 this 仍然是 a,但是 a 中没有 name 属性,故报错。这说明了 this 永远指向最后调用它的那个对象。

var name = "windowsName";
var a = {
  // name: "Cherry",
  fn: function () {
    console.log(this.name); // undefined
  },
};
window.a.fn();
1
2
3
4
5
6
7
8

在通过将函数赋值的方式调用呢?可以看出首先将 a 中的 fn 赋值给了 f,但是并没有调用,而通过 f()调用时,此时 this 指向 window。

var name = "windowsName";
var a = {
  name: null,
  // name: "Cherry",
  fn: function () {
    console.log(this.name); // windowsName
  },
};

var f = a.fn;
f();
1
2
3
4
5
6
7
8
9
10
11

提示

this 的指向并不是在创建的时候可以确定的,在 es5 中,this 永远是指向最后调用的它的那个对象。

var name = "windowsName";

function fn() {
  var name = "Cherry";
  innerFunction();
  function innerFunction() {
    console.log(this.name); // windowsName
  }
}

fn();
1
2
3
4
5
6
7
8
9
10
11

# 改变 this 的指向

  • 使用 ES6 的箭头函数
  • 在函数内部使用_this = this;
  • 使用 apply、call、bind;
  • new 实例化一个对象。
var name = "windowsName";

var a = {
  name: "Cherry",

  func1: function () {
    console.log(this.name);
  },

  func2: function () {
    setTimeout(function () {
      this.func1();
    }, 100);
  },
};

a.func2(); // this.func1 is not a function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

上述代码中执行过程中,找不到 func1 是啥子情况呢?因为最后调用 setTimeout 的对象是 window,在 window 中并没有 func1 函数,如何可以改变 this 指向呢?

# 箭头函数

var name = "windowsName";

var a = {
  name: "Cherry",

  func1: function () {
    console.log(this.name);
  },

  func2: function () {
    setTimeout(() => {
      this.func1();
    }, 100);
  },
};

a.func2(); // Cherry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

箭头函数的this始终指向函数定义时候的this,而非执行时的。因为箭头函数中没有this的绑定,必须通过查找作用域链来解决其值,如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this是undefined。

var name = "windowsName";
function func1(){
console.log(this.name);
}

var a = {
  name: "Cherry",

  func1: function () {
    console.log(this.name);
  },

  func2: function () {
    setTimeout(() => {
      this.func1();
    }, 100);
  },
};

let b = a.func2; 

b();  // windowsName
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上述代码输出结果是windowsName,此时将a中的func2函数赋值给了b,执行b()时,此时this.func1()的this指向window。这也说明了,箭头函数的this并没有在定义的时候确认,而是在执行的时候才确认,通过向上查找作用域链来确定。

# 函数内部使用_this = this

原理:先将调用这个函数的对象保存在变量_this中,然后在函数中都使用_this,这样的话this的指向仍然是调用这个函数时候的this了。

var name = "windowsName";

var a = {

    name : "Cherry",

    func1: function () {
        console.log(this.name)     
    },

    func2: function () {
        var _this = this;
        setTimeout( function() {
            _this.func1()
        },100);
    }

};

a.func2()       // Cherry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在这个例子中,在func2中首先设置var _this = this,这里的this指向的是调用func2的对象a,为了防止在func2中的setTimeout被window调用而导致在setTimeout中的this变为window。所以提前将this(指向变量a)赋值给一个变量_this,这样在func2中使用的_this就是指向对象a了。

如果是这样呢?这样时候func2中的this由于是在window中调用的,故this指向的是window,所以这种方法只能保证在对象中调用方法时候,this指向能保证是在对象内部。

var name = "windowsName";

var a = {

    name : "Cherry",

    func1: function () {
        console.log(this.name)     
    },

    func2: function () {
        var _this = this;
        setTimeout( function() {
            _this.func1()
        },100);
    }

};

let c = a.func2;
c();  // Uncaught TypeError: _this.func1 is not a function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# apply、call、bind

var name = "windowsName";

var a = {
    name : "Cherry",

    func1: function () {
        console.log(this.name)     
    },

    func2: function () {
        setTimeout(  function () {
            this.func1()
        },100);
    }

};

a.func2()     // this.func1 is not a function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

三者都可以改变this的指向,但有些区别。

apply和call的区别是call方法接收的是若干个参数列表,而apply接收的是一个包含多个参数的数组。

bind与apply和call的区别是:bind会创建一个新的函数,需要手动去调用。

# JS中函数调用

上面说到this指向是最后调用它的那个对象,当然函数也是对象,如果是函数调用的话,就要考虑函数调用的方法。

  • 作为一个函数调用;
  • 函数作为方法调用;
  • 使用构造函数调用函数;
  • 作为函数方法调用函数(call、apply)。

# 作为一个函数调用

var name = "windowsName";
function a() {
    var name = "Cherry";

    console.log(this.name);          // windowsName

    console.log("inner:" + this);    // inner: Window
}
a();
console.log("outer:" + this)         // outer: Window
1
2
3
4
5
6
7
8
9
10

作为简单函数,不属于任何一个对象,就是一个函数,在浏览器的非严格模式下默认是属于全局对象window的。但是全局的函数,很容易产生命名冲突,不建议使用。

# 函数作为方法调用

var name = "windowsName";
var a = {
    name: "Cherry",
    fn : function () {
        console.log(this.name);      // Cherry
    }
}
a.fn();
1
2
3
4
5
6
7
8

常见的情况是将函数作为对象的方法使用。这里定义了一个对象a,对象a有一个属性name和方法fn;然后对象a通过.方法调用了fn方法。this永远指向最后调用它的那个对象,所以在fn中的this就是指向a的。

# 使用构造函数调用函数

如果函数调用前使用了new关键字,则是调用了构造函数。这看起来像是创建了新的函数,但是实际上是JavaScript函数是重新创建的对象。

// 构造函数:
function myFunction(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
}

// This    creates a new object
var a = new myFunction("Li","Cherry");
a.lastName;                             // 返回 "Cherry"
1
2
3
4
5
6
7
8
9

这就涉及到new的过程了:

  1. 创建一个空对象,作为将要返回的实例对象;
  2. 将这个空对象的原型,指向构造函数的prototype属性;
  3. 将这个空对象赋值给函数内部的this关键字;
  4. 开始执行构造函数内部的代码;
  5. 如果构造函数内有返回值且为对象类型,则返回该对象;否则返回上面创建的实例对象。
function Person(name,age) {
    this.name = name;
    this.age = age;
}

function _new() {
    const args = [].slice.call(arguments);
    const constructor = args.shift();
    const context = Object.create(constructor.prototype);
    const result = constructor.apply(context, args);
    return (typeof result === 'object' && result != null) ? result : context;
}

// 通过自定义_new 返回实例
var actor = _new(Person, '张三', 28);
console.log(actor.name) // 张三

// 通过自定义_new2 返回实例
var actor2 = _new2(Person, ['李四', 29]);
console.log(actor2.name) // 李四
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 作为函数方法调用

在JavaScript中,函数也是对象,所以函数也有自己的属性和方法。比如说call()、apply()是预定义的函数方法,这两个方法可以用于调用函数,第一个参数必须是对象本身。

除了预定义的方法,也可以自定义一些方法,在非严格模式下,调用函数方法第一个参数的值是null或undefined,它将使用全局对象替代。

var name = "windowsName";

function fn() {
    var name = 'Cherry';
    innerFunction();
    function innerFunction() {
        console.log(this.name);      // windowsName
    }
}

fn()
1
2
3
4
5
6
7
8
9
10
11

上述代码作为函数方法调用,没有挂载在任何对象上,在非严格模式下this指向window。


参考文章:

  1. this、apply、call、bind (opens new window) 后续:「面试必会」手写35道JS面试高频题(一) (opens new window) 「面试必会」中高级前端必会的手写面试题(二) (opens new window)
编辑 (opens new window)
上次更新: 2022/05/17, 21:50:47
JS变量声明的六种方式
==和===运算符区别

← JS变量声明的六种方式 ==和===运算符区别→

最近更新
01
一些有意思的类比
06-16
02
the-super-tiny-compiler解析
06-06
03
计算机编译原理总概
06-06
更多文章>
Theme by Vdoing | Copyright © 2021-2022
蜀ICP备2021023197号-2
Simonzhans | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
  • 飙升榜
  • 新歌榜
  • 云音乐民谣榜
  • 美国Billboard榜
  • UK排行榜周榜