• 深入解析JavaScript中的this关键字


    如果问初学者js中什么东西比较难懂,很多回答会是this关键字。this在不同场景下所指向的对象不同,这就有一种扑朔迷离的感觉,放佛魔法一般神秘:this到底是什么?这里有四种绑定规则。

    1. 默认绑定

    默认绑定是无法应用其他调用规则时的绑定方式,看如下代码:

    1 var a = 1;
    2 
    3 function foo(){
    4     console.log(this.a);
    5 }
    6 
    7 foo(); // 1
    1 "use strict"; 
    2 
    3 var a = 1;
    4 
    5 function foo(){
    6     console.log(this.a);
    7 }
    8 
    9 foo(); // TypeError: Cannot read property 'a' of undefined

    这是最基本的一个函数调用,在第一张图中,非严格模式下,this绑定到全局对象,因此this.a指向全局变量a。第二张图中,严格模式下,全局对象无法使用默认绑定,因此this会绑定到undefined。

    那么如何判断是默认绑定呢,其实很简单,我们观察foo的调用位置,这里foo是直接被调用的,foo没有被引用到任何其他对象或着被显式绑定到指定对象(显式绑定稍候会说明),因此只能使用默认绑定规则。

     2. 隐式绑定

     隐式绑定需要考虑调用位置是否有上下文对象,或者说是够被某个对象包含或拥有,比如以下代码:

     1 function foo(){
     2     console.log(this.a);
     3 }
     4 
     5 var obj = {
     6     a: 1,
     7     foo: foo
     8 };
     9 
    10 obj.foo(); // 1

    在声明obj时,包含了foo,因此调用obj.foo()时,this绑定到obj,this.a就是obj.a。如果有多个层级的包含关系,this会绑定到最后一层的上下文对象上,比如以下代码:

     1 function foo(){
     2     console.log(this.a);
     3 }
     4 
     5 var obj2 = {
     6     a: 2,
     7     foo: foo
     8 };
     9 
    10 var obj1 = {
    11     a: 1,
    12     obj2: obj2
    13 };
    14 
    15 obj1.obj2.foo(); // 2

    此时this会绑定到obj2上。

     还有一种情况,叫隐式绑定丢失,看如下代码:

     1 function foo(){
     2     console.log(this.a);
     3 }
     4 
     5 var obj = {
     6     a: 1,
     7     foo: foo
     8 };
     9 
    10 var a = 'global';
    11 
    12 var bar = obj.foo;
    13 
    14 bar(); // 严格模式下是undefined,非严格模式下是global

    这里指定了bar为obj.foo的一个别名(或者说引用),但是很重要的是,这里bar实际上引用的是foo本身,所以这里调用bar()相当于一个默认绑定,适用于上面讲到的默认绑定规则。如果确实需要函数别名并且把this绑定到指定的对象上,可以使用显式绑定,比如bind、call、apply之类的,后面会陆续谈到。

    隐式绑定丢失还会出现在传入回调函数的时候:

     1 function foo(){
     2     console.log(this.a);
     3 }
     4 
     5 function caller(func){
     6     func();
     7 }
     8 
     9 var obj = {
    10     a: 1,
    11     foo: foo
    12 };
    13 
    14 var a = 'global';
    15 
    16 caller(obj.foo); // 严格模式下是undefined,非严格模式下是global

    在前端的js编程中,由于是事件驱动的,调用回调函数经常发生在用户交互之后,由于绑定丢失,我们经常需要手动把this绑定到某个对象上。

    3. 显式绑定

    如果我们想在某个对象上强制调用函数,可以是用显式绑定。js中的函数的原型是Function对象,它提供了一些通用方法。就显式绑定来说,我们可以使用apply和call这两个方法,具体用法是:

    1 func.apply(obj, [arg1, arg2,...]);
    2 func.call(obj, arg1, arg2,...);

    先不用纠结后面参数的格式(其实apply和call只是在传参格式上不一样而已),apply和call都是将func的this绑定到第一个参数obj上。看以下代码:

     1 function foo(){
     2     console.log(this.a);
     3 }
     4 
     5 var obj = {
     6     a: 1
     7 };
     8 
     9 var a = 'global';
    10 
    11 foo.call(obj); // 1

    此时foo的this绑定到了obj上面。 

    apply和call的第一个参数也可以是null,即不绑定到任何对象,但实际上这样会绑定到全局对象:

    1 function foo(){
    2     console.log(this.a);
    3 }
    4 var obj = {
    5     a: 1
    6 };
    7 var a = 'global';
    8 foo.call(null); // 严格模式下是undefined,非严格模式下是global

    虽然apply和call可以把this绑定到指定对象,但是还是没有解决回调函数的问题,因为apply和call都是在当下立刻执行,而回调函数的执行时间是不确定的。而且回调函数的上下文也是不确定的,在回调函数的上下文中可能很难获得我们想要的那个this绑定对象。

    为了解决回调函数绑定丢失的问题,我们可以使用硬绑定bind。bind很有用,它可以对this强制绑定一个对象,而且绑定后无法修改。这对我们事件驱动的编程模型很有帮助,可以大量运用在回调函数中。另外bind在js的函数式编程中也是一项利器。看以下代码:

     1 function foo(){
     2     console.log(this.a);
     3 }
     4 var obj1 = {
     5     a: 1
     6 };
     7 var obj2 = {
     8     a: 2
     9 };
    10 var bar = foo.bind(obj1); // bar的this永远只会指向obj1
    11 bar(); // 1
    12 bar.call(obj2); // 1 因为无法改变bind后的this绑定,所以还是1

    4. new绑定

    new绑定是使用new操作符对函数进行调用产生的绑定。js中的new和其他面向对象编程语言的new不同。一般的OOP中new操作符会调用类的构造函数,生成一个全新的类实例。js中没有类,也没有构造函数,使用new操作符调用函数实际上做了以下4件事情: 

        (1) 创建一个全新的对象。

        (2) 这个新对象会和它的原型对象进行连接。

        (3) 这个新对象会被绑定到函数调用的this。

        (4) 如果函数没有返回其他对象,那这个new表达式将自动返回这个新对象。

    代码如下:

    1 function foo(a){
    2     this.a = a;
    3 }
    4 var bar = new foo(1);
    5 console.log(bar.a); // 1

    总结

    本文介绍了this的四种情况,需要再强调的是,判断this的绑定,不要看函数被定义的地方,而要看函数被调用的地方,或者说上下文。

    本blog已搬迁至https://nullcc.github.io/
  • 相关阅读:
    Flutter form 的表单 input
    FloatingActionButton 实现类似 闲鱼 App 底部导航凸起按钮
    Flutter 中的常见的按钮组件 以及自 定义按钮组件
    Drawer 侧边栏、以及侧边栏内 容布局
    AppBar 自定义顶部导航按钮 图标、颜色 以及 TabBar 定义顶部 Tab 切换 通过TabController 定义TabBar
    清空路由 路由替换 返回到根路由
    应对ubuntu linux图形界面卡住的方法
    [转] 一块赚零花钱
    [转]在树莓派上搭建LAMP服务
    ssh保持连接
  • 原文地址:https://www.cnblogs.com/nullcc/p/5828466.html
Copyright © 2020-2023  润新知