• javascript 函数的4种调用方式与 this(上下文)的指向


    前言:这是笔者学习之后自己的理解与整理。如果有错误或者疑问的地方,请大家指正,我会持续更新!

       javascript 中作用域链和 this(上下文)的指向是很容易混淆的,简单的说就是:

    1. 作用域链取决于函数声明的位置,函数声明之后,从函数内部往外,一直到window,这就是它的作用域链,与函数调用位置无关
    2. this 指向函数调用时的对象,如果是独立调用,那就是指向 window,与函数声明的位置无关

      函数调用的方式有4种,this 也就有4种指向

    1. 独立调用:func(),函数独立调用,this指向window,;
    2. 方法调用:obj.func(),函数作为obj的一个方法(属性)调用,this指向obj;
    3. 构造函数调用:new Func(),如果在一个函数前面带上 new 关键字来调用, 那么背地里将会创建一个连接到该函数的 prototype 的新对象,this指向这个新的对象;
    4. call、apply、bind调用:func.call(obj,value1,value2);  func.apply(obj,[value1,value2]); func.bind(obj,value1,value2)();  func.bind(obj)(value1,value2); 动态改变 this 的指向 obj;

    独立调用和方法调用

      全局环境中,this 默认指向到 window;

      函数独立调用(不论这个函数在哪调用),this 默认指向到 window;

      当函数被作为对象的方法(对象的一个属性)调用时,this 指向该对象;

      函数只有调用了之后才有 this 的概念,不然它只是代码而已,我们经常把函数传给一个变量,如果后面没加括号,就只是传了个函数,而不是执行结果;

            <script type="text/javascript">
                console.log(this === window);//true  全局环境中,this默认指向到window
                
                function abc(){
                    console.log(this === window);
                }
                abc();//true   函数abc独立调用(不论这个函数在哪调用),this默认指向到window
    
                function outer(){
                    function inner(){
                        console.log(this === window);
                    }
                    inner();
                }
                outer();//true  函数inner独立调用(不论这个函数在哪调用),this默认指向到window
                
                var o = {
                    m: function(){
                        return this;
                    },
                    n: function(){
                        function test(){
                            return this;
                        }
                        return test(); //函数test独立调用(不论这个函数在哪调用),this默认指向到window
                    }
                };
                console.log(o.m());//Object o{...}  当函数被作为对象的方法(对象的一个属性)运行时,this指向该对象
                console.log(o.n());//Window{...}  函数test独立调用,this默认指向到window
            </script>

      当函数被作为对象的方法调用时,可以使用 this 访问自己所属的对象,所以它能从对象中取值或对对象进行修改;

      this 到对象的指向发生在调用的时候,如果函数没有被调用,则不会更改对象的变量;

      通过 this 可取得它们所属对象的上下文的方法称为公共方法;

            <script type="text/javascript">
                var o = {
                    a: 1,
                    m: function(){
                        return this;
                    },
                    n: function(){
                        this.a = 2; 
                        //当函数被作为对象的方法调用时,可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改。
                        //this到对象的绑定发生在调用的时候。所以这里的函数 n 如果没有调用过,那么外部的 a 是不会被改变的
                    }
                };
                console.log(o.m().a);//1
                o.n();
                console.log(o.m().a);//2
            </script>

      

      如果想访问这个外部函数的 this 值,需要将 this 的值保存在一个变量里,内部函数就可以通过作用域链找到这个变量。

            <script type="text/javascript">
                var o = {
                    m: function(){
                        console.log(this); //{} 当函数被作为对象的方法(对象的一个属性)运行时,this指向该对象
                    },
                    n: function(){
                        console.log(this);//{}
                        
                        var that = this;  
                        //如果想访问这个外部函数的this值,需要将this的值保存在一个变量里,内部函数就可以通过作用域链找到这个变量。
                        
                        function test(){
                            console.log(this);//window
                            console.log(that);//{}
                        }
                        return test(); //函数test独立调用(不论这个函数在哪调用),this默认指向到window
                    }
                };
                o.m();
                o.n();
            </script>

    构造函数调用

      如果一个函数用 new 关键字调用,那么这个函数就是构造函数,并且背地里会创建一个连接到该函数的 prototype 的新对象,this 指向这个新对象;

      如果构造函数没有形参,实例化的时候是可以不带()的;如  var  a = Func; 或者  var a = Func(); 两种都可以;

      同时我们在构造函数的时候有个约定(不是规范),首字母大写,以避免忘了写new关键字或者在普通函数前面加new;

      new 关键字的作用就是执行一个构造函数,并返回一个对象实例。使用 new 命令,它后面的函数的函数调用和普通函数调用就不一样了,步骤如下:

    1. 创建一个空对象,作为将要返回的对象实例;
    2. 将空对象的原型 _proto_ 指向构造函数的 prototype 属性;
    3. 将构造函数内部的this关键字指向空对象;
    4. 执行构造函数内部的代码;

      就是说 this 指向这个新对象,构造函数内所有针对 this 的操作,都会发生在这个新对象上;

            <script type="text/javascript">
                // 创建一个名为Person 的构造函数,它构造一个带有user 和age 的对象
                var Person = function (user,age) {
                    this.user = user;
                    this.age = age; 
                };
                
                // 构造一个Person 实例 ,并测试
                var shane = new Person ('shane',25);
                console.log(shane.user);//shane
                console.log(shane.age);//25
            </script>

      javascript 中构造函数是不需要有返回值的,可以认为构造函数和普通函数之间的区别就是:构造函数没有 return 语句,普通函数可以有 return 语句;

      构造函数使用 this 关键字定义变量和方法,当 this 遇到 return 的时候,会发生指向不明(调用结果不明)的问题:

    1. return 返回的不是一个对象,this 还是指向实例(新对象),调用结果也还是新对象;
    2. return 返回的是一个对象,this 就指向这个返回的对象,调用结果就是这个返回的对象;
    3. return 返回的是 null,this 还是指向实例,调用结果也还是新对象;
    <script type="text/javascript">
                var Person = function(){
                    this.user = 'shane';
                    return 
                }
                var shane = new Person;
                console.log(shane.user);//shane   return没有返回值,this还是指向实例(新对象),调用结果也还是新对象;
                
                var Color = function(){
                    this.red = 'red';
                    return 'hello world';
                }
                var redColor = new Color;
                console.log(redColor.red);//red   return返回的是一个基本类型的字符串(原始值),this还是指向实例(新对象),调用结果也还是新对象;
                
                var Size = function(){
                    this.size = 'big';
                    return {};
                }
                var sizeBig = new Size;
                console.log(sizeBig.size);//undefined   return返回的是一个对象,this就指向这个返回的对象,调用结果就是这个返回的对象;
            </script>

    间接调用

      通过 call()、apply()、bind() 方法把对象绑定到 this 上,叫做显式绑定。对于被调用的函数来说,叫做间接调用

    1. call、apply、bind三者的第一个参数都是this要指向的对象,
    2. bind 只是返回函数,还未调用,所以如果要执行还得在后面加个();call、apply 是立即执行函数;
    3. 三者后面都可以带参数,call 后面的参数用逗号隔开,apply 后面的参数以数组的形式传入;bind则可以在指定对象的时候传参,和 call 一样,以逗号隔开,也可以在执行的时候传参,写到后面的括号中;func.call(obj,value1,value2);  func.apply(obj,[value1,value2]); func.bind(obj,value1,value2)();  func.bind(obj)(value1,value2);
            <script type="text/javascript">
                var add = function (a,b) {
                    return a+b;
                }; 
                
                var arr = [2,3];
                
                var sum0 = add.apply(null, arr);//apply后面的参数以数组的形式传入
                var sum1 = add.call(null,arr[0],arr[1]);//call后面的参数用逗号隔开
                var sum2 = add.bind(null,arr[0],arr[1]);//bind后面的参数可以和call一样,用逗号隔开
                var sum3 = add.bind(null); //bind调用的时候也可以在执行的时候传参
                
                console.log(sum0);//5
                console.log(sum1);//5
                console.log(sum2);//function (a,b) {return a+b;};    bind只是返回函数,还未调用,
                console.log(sum2());//5   所以bind调用的时候,如果要执行还得在后面加个()
                console.log(sum3(arr[0],arr[1]));//5   bind调用的时候也可以在执行的时候传参
            </script>

    严格模式下

    1. 在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window。 除非明确把函数添加到某个对象或者调用 apply() 或 call(),否则 this 值将是 undefined;
    2. 所以我们可以手动添加 window.函数(),将 this 指向 window;
            <script type="text/javascript">
                'use strict' //严格模式
                function a(){
                    console.log (this);  
                }
                a();//undefined
                //在严格模式下,未指定环境对象而调用函数,则 this 值不会转型为 window , this 值将是 undefined。 
                //除非明确把函数添加到某个对象或者调用 apply()或 call()使用对象冒充。
                
                window.a();//window
                //所以我们可以手动添加 window.函数(),来改变this指向window
                
                (function(){
                    console.log(this);//undefined
                })();
                
                setTimeout(function(){
                    console.log(this);//Window
                },0);
                //setTimeout是window的一个方法(属性),所以这里可以写成window.setTimeout
           //所以不管是严格模式还是非严格模式,setTimeout里的this始终指向window
           //但是有时候我们会发现setTimeout中的this也有其他情况,这只是误导,当然如果你不确定的话,直接console.log(this)一下,分分钟解决 </script>
  • 相关阅读:
    一、基础篇--1.1Java基础-反射的用途和实现
    一、基础篇--1.1Java基础-抽象类和接口的区别
    一、基础篇--1.1Java基础-重载和重写的区别
    一、基础篇--1.1Java基础-String、StringBuilder、StringBuffer
    一、基础篇--1.1Java基础-包装类的装箱和拆箱
    一、基础篇--1.1Java基础-int 和 Integer 有什么区别,Integer的值缓存范围
    一、基础篇--1.1Java基础-Exception、Error、RuntimeException与一般异常有何异同
    一、基础篇--1.1Java基础-final, finally, finalize 的区别
    c++ 中 *++ptr,++*ptr等的区别
    c++ new与char*,动态数组,sizeof的注意点总结
  • 原文地址:https://www.cnblogs.com/sspeng/p/6633204.html
Copyright © 2020-2023  润新知