• javascript中this的指向问题


     this存在于哪里?

    this是一个很特别的关键字,被自动定义在所有函数的作用域中。

     

    为什么要使用this,好处是什么?

    能将“显式”传参转化为“隐式”传参。更简洁且易于复用。

    function say1(){
                let message = "Hello,My name is " + this.name; 
                console.log(message);
            }
               var I = {name: "Mary"};
               var You = {name: "Lily"};
               say1.apply(I);    //Hello,My name is Mary
               say1.apply(You);        //Hello,My name is Lily
    
               //对比:
               function say2(obj){
                let message = "Hello,My name is " + obj.name; 
                console.log(message);
            }
            var I = {name: "Mary"};
               var You = {name: "Lily"};
               say2(I);        //Hello,My name is Mary
               say2(You);        //Hello,My name is Lily

    这么好。该如何使用,又有什么难点呢?

     

    我们对this有什么误解呢?

     1、认为this指向函数自身

    foo.count = 0;
    var count = 0;
    function foo(num){
    console.log("foo: "+num);
    console.log(this);
    this.count++;
    }

    for(let i=0;i<5;i++){
    if(i>2){
    foo(i); //foo: 3 foo: 4
    }
    }
    //与我们设想的结果不一样,显然,this并不是指向函数自身
    console.log(foo.count); //0
    console.log(count); // 2  (增加数量的是window下的count)

            //解决方法1:(回避this问题)
            bar.count = 0;
            function bar(num){
                console.log("bar: "+num);
                bar.count++;
            }
    
            for(let i=0;i<5;i++){
                if(i>2){
                    bar(i);        //bar: 3      bar: 4
                }
            }
            console.log(bar.count);        //2
    
            //解决方法2:修改this的指向
            baz.count = 0;
            function baz(num){
                console.log("baz: "+num);
                this.count++;
            }
            for(let i=0;i<5;i++){
                if(i>2){
                    baz.call(baz,i);        //baz: 3      baz: 4
                }
            }
            console.log(baz.count);        //2

      2、认为this指向该函数的作用域。

    var a = 454;
    function foo(){
    var a = 2;
    bar();
    }

    function bar(){
    console.log(this.a);
    }
    //bar() 中的this 是无法访问到foo() 词法作用域中的变量a的
    foo(); //454 (访问到的是最外层window下的变量a)

    那么,

    this的指向是什么呢?

    this 的原理类似于我们之前在  作用域  里面提到的动态作用域,是在运行时才进行绑定的,也就是说它的指向完全取决于函数在哪里被调用。

    //当前 调用位置 是 当前调用栈 的上一级
            function foo(){
                //当前调用栈是:foo ; 当前调用位置是:全局作用域
                console.log('foo');
                bar();
            }
            function bar(){
                //当前调用栈是:foo>bar ; 当前调用位置是:foo
                console.log('bar');
                baz();
            }
            function baz(){
                //当前调用栈是:foo>bar>baz ; 当前调用位置是:foo>bar
                console.log('baz');
            }
            
            foo();    

     

    this的绑定规则是什么呢?有没有什么规律

    有四种,分别是 : 默认规则、隐式绑定、显示绑定、new绑定。

    1、默认绑定:直接使用不带任何修饰的函数引用进行调用,才使用默认绑定,指向全局对象。(注意:严格模式下,不能使用

    function foo(){
                console.log(this.a);
            }
            var a = 2;
            foo();        //2
    
            //严格模式下不能使用默认模式
            function bar(){
                "use strict";
                console.log(this.a);
            }
            var a = 2;
    
            bar();        //Cannot read property 'a' of undefined 

    2、隐式绑定:

    对象属性引用链:最后一层控制this上下文。常出现的问题(通常出现在调用函数别名和调用回调函数)是隐式丢失,使用了默认规则

    function foo(){
                console.log(this.a);
            }
            const obj1 = {
                a: 1,
                foo: foo
            };
            const obj2 = {
                a: 2,
                foo: obj1.foo,
                bar:obj1
            };
    
            var a = 3;
            obj2.foo();        //2
            obj2.bar.foo();        //1
    
            var baz = obj1.foo;
            //隐式丢失(调用函数别名),使用默认规则,这时的this指向全局对象
            baz();    //3
            //对比:此时使用的隐式规则
            obj1.foo();        //1
    
            function doFun(fn){
                fn();
            }
            //隐式丢失(调用回调函数),使用默认规则,这时的this指向全局对象
            doFun(obj1.foo);    //3

    3、显式绑定

    与原型有关,可以通过call() 和 apply() 来修改this的指向

    function foo(b,c){
                console.log(this.a);
                console.log(b,c);
            }
    
            var a = 0;
            foo(2,3);        //0     2 3   
            var obj = {
                a:'obj',
                b:'foo-a',
                c:'foo-b'
            };
            //call() 和 apply() 只有参数格式不同的区别
            foo.call(obj,obj.b,obj.c);    //obj     foo-a foo-b
            foo.apply(obj,[obj.b,obj.c]);    //obj     foo-a foo-b

    call() 的实现原理:

    Function.prototype.myCall = function(context,...args){
                let cxt = context || window;
                //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this)
                //新建一个唯一的Symbol变量避免重复
                let func = Symbol();
                console.dir(this);  //getName()  ---> 这里是隐式绑定,
                cxt[func] = this;
                args = args ? args : []
                //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向
                const res = args.length > 0 ? cxt[func](...args) : cxt[func]();
                //删除该方法,不然会对传入对象造成污染(添加该方法)
                delete cxt[func];
                return res;
            }
    
            let obj = {
                name: "obj",
                getName(){
                    console.log(this.name);
                }
            };
            let obj1 = {
                name: "obj1"
            };
    
            obj.getName.myCall(obj1);  // obj1

    3.1 显式绑定的变种(硬绑定)能解决前面的隐式丢失的问题

    var a = 1;
            function foo(b=''){
                console.log(this.a,b);
            }
            var obj1 = {
                a: 2,
                foo:foo
            };
            var obj2 = {
                a: 3,
                b:obj1.foo,
                c:obj1
            };
            
            //隐式规则
            obj2.b();    //3
            obj2.c.foo();    //2
            //默认规则
            foo();        //1
    
            //隐式丢失的两种情况: 函数别名 和 回调函数
            var bar = obj2.b;
            bar();   //1
    
            setTimeout(obj2.c.foo,100);        //1
    
            //解决方法:硬绑定
            var bar1 = function(){
                obj2.b.call(obj2,arguments[0]);
            }
            bar1('bar1');        //3 "bar1"
    
            var bar2 = function(){
                obj2.c.foo.call(obj1,arguments[0]);
            }
            bar2('bar2');        //2 "bar2"

    4、new 绑定

    使用new操作符调用的函数被我们称之为  “构造函数 ”。它与普通函数并没有什么区别,只是被new操作符调用而已。发生构造函数调用时,会自动执行下面的操作:

      (1)创建(或者说构造)一个全新的对象。

      (2)这个新对象会被执行【原型】连接。

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

      (4)如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

    function foo(){
                this.a = 2;
            }
            
            bar = new foo();
            console.log(bar.a);        //2

     

    上面这四种规则的优先级是什么呢?

    new绑定 > 显示规则 > 隐式规则 > 默认规则

    function foo(){
                console.log(this.a);
            }
            var obj1 = {
                a: 1,
                foo: foo,
                baz:baz
            };
            var a = 2;
            //隐式
            obj1.foo();        //1
            //显示
            obj1.foo.call(window);        //2
            //结论:显式 > 隐式
    
            function baz(num){
                this.a = num;
            }
            obj1.baz('隐式规则');
            console.log(obj1.a);        //隐式规则
            var bar = new obj1.baz("new模式");
            console.log(obj1.a);        //隐式规则
            console.log(bar.a);        //new模式
            //结论: new模式 > 隐式
            
            console.log("..........");
            var fn = baz.bind(window);
            fn(3);
            console.log(a);        //3
            var obj = new fn(100);
            //等价于
            var obj1 = new (baz.bind(window))(100);
            console.log(obj1.a);    //100
            console.log(obj.a);        //100
            //结论: new模式 > 显式

     

    规则以外的意外

    万事总有例外,并不总是遵循上面 的规则。如一下情况:

    1、被忽略的this

      在调用call 或 apply 时, 第一参数传入了null 或 undefined 来进行this 的绑定,此时使用的是默认规则。 一般这么做,只是占位而已,函数中都不会调用到this,但不排除某些函数用到了this。所以为了安全起见,还是传入一个空的对象更好。

    //通常的情况
            function foo(a,b){
                console.log(`${a}...........${b}`);
            }
            foo.call(null,3,4);        //3...........4
    
            //最好使用
            var NONE = Object.create(null);
            foo.call(NONE,3,4);        //3...........4

    2、es6中的箭头函数

      不适用上面的四个规则,是根据外层的作用域来确定 this 的,用更常见的词法作用域来代替传统的this机制。

    var a = 1;
            function fun(fn){
                fn();
            }
            function foo(){
                fun(()=> {
                    console.log(this.a);
                });
            }
            var obj = {
                a: 2
            };
            //这里的箭头函数跟外层的 foo() 指向一致
            foo();        //1
            foo.call(obj);        //2
    
            //等价于(定义变量替换this,绕过this机制)
            function foo1(){
                var self = this;
                fun(function (){
                    console.log(self.a);
                });
            }
            //等价于(使用bind()修改this指向)
            function foo2(){
                fun(function (){
                    console.log(this.a);
                }.bind(foo2));
            }
            //效果跟上面的箭头函数是一样的
            foo1();        //1
            foo1.call(obj);        //2
            foo2();        //1
            foo2.call(obj);        //2

    3、隐式丢失

      在前文介绍隐式规则的时候已经有提到了,这里就不再多说。

     

    总结

      this的内容大概就这么多。重点是看函数运行时来确定this 的绑定,绑定的规则有:new绑定(使用new关键字生成的实例) > 显式规则 (通过call() 、apply()、bind()等方法来修改this的指向)> 隐式规则(通过对象属性的引用,由最后一层控制this的指向) > 默认规则(其他规则都不适用时,使用默认规则,一直都指向全局对象)。不过有些this的绑定并不遵守上面的规则,例如:ES6中的箭头函数、使用函数别名和回调函数后出现的隐式丢失等情况。

  • 相关阅读:
    泛型
    BigInteger和BigDecimal大数相加问题
    集合(Collection,set,list,map)
    [转]如何从MySQL官方Yum仓库安装MySQL5.6
    CentOS Linux使用crontab运行定时任务详解
    [转]Mysql自动备份并保存近15天记录脚本
    centos6.5 mysql安装+远程访问+备份恢复+基本操作+卸载
    vsftpd安装
    [转]CENTOS 6.5 配置YUM安装NGINX+服务器负载均衡
    [转]apache的源码安装详细过程全纪录
  • 原文地址:https://www.cnblogs.com/zxn-114477/p/14453576.html
Copyright © 2020-2023  润新知