zoukankan      html  css  js  c++  java
  • JavaScript 函数调用和this指针

    函数调用和this指针

    1. 全局环境的this指针

    浏览器全局环境下this指向window对象

    console.log(this);
    //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
    

    nodejs环境下this指向global对象

    2. 函数中的this指针

    2.1 全局环境下函数调用

    非严格模式this指向window,严格模式除箭头函数外指向undefine

    //a.js 非严格模式
    function fun1(){console.log(this)}
    fun1();//window
    
    (function(){console.log(this)})() //window
    
    var fun2 = ()=>{console.log(this)}
    fun2();//window
    
    //b.js 严格模式
    'use strict';
    function fun1(){console.log(this)}
    fun1();//undefined
    
    (function(){console.log(this)})() //undefined
    
    var fun2 = ()=>{console.log(this)}
    fun2();//window
    

    2.2 作为对象方法调用

    function作为对象属性时,this指针指向对象的实例

    var obj1 = {
        name: 'a',
        func1:function(){console.log(this.name);}
    }
    obj1.func1();//a
    //等同于
    function func2(){console.log(this.name);}
    var obj2 = {
        name: 'b',
        funRef:func2
    }
    obj2.funRef();
    

    2.3 构造函数调用

    构造函数调用时,this指针指向函数的实例(function也是对象的一种)

    function func3(c){
        this.c = c;
        console.log(this.c);//c
        console.log(this);//func3 {c: "c"}
    }
    var finst3 = new func3('c');
    

    2.4 同步回调函数

    同步回调函数中的this指针服从上面1-3点的规则

    function callback()
    {
        console.log(this.d);//undefined
        console.log(this);//window
    }
    function func4(cb){
        cb.d = 'd';    
        cb();
    }
    func4(callback);
    

    2.5 异步回调函数

    关于JavaScript是单线程执行就不多提了,这里详细分析一下对于异步操作的回调函数中this的指向问题。
    提到异步操作就不得不提Event Loop,其详细介绍可以参见阮一峰大神的这篇

    简单来说就是:

    • 所有异步函数的执行被放在“任务队列”中由另外一个单独的线程执行。当异步操作有结果时,会通知主线程调用该异步操作的回调函数
    注意:这里就有问题了,当主线程调用异步操作的回调函数时,是以什么环境和作用域来执行的呢?

    答案是: 以全局环境和执行回调函数时的作用域链来执行回调函数。

    参考以下代码:

    setTimeout(function(){
        console.log(this);//1秒后输出window
        }, 1000);
    
    //再看
    var asyncFun1 = {
        propA:"a",
        synFun:function() {        
            console.log(this.propA);
            console.log(this);        
        },
        asynFun: function(){setTimeout(this.synFun, 1000)}
    }
    asyncFun1.synFun();//输出依次是
    //a
    //{propA: "a", synFun: ƒ, asynFun: ƒ}
    asyncFun1.asynFun();
    //undefined
    //window
    
    //再再看
    var asyncFun2={
      propB:"b",
      asynFun:function(){
          var c = 'string c';
          setTimeout(function(){
              console.log(c);
          },1000);
        }
    }
    asyncFun2.asynFun();//输出'string c'
    

    注意为什么会输出'string c': 虽然回调函数运行是在全局环境(this指向window),但是在其作用域链中是能够访问到变量c的。

    常见的异步操作包括:

    • setTimeout/setInterval
    • XMLHTTPRequest
    • jQuery AJAX(这里的this被处理过并不指向window)
    • Fetch
    • Promise

    关于如何在异步操作的回调函数中使用正确的this指针,往下看。

    3. 在异步函数回调中正确使用this指针

    通过上面的分析我们知道在异步回调函数中this会指向window,那么我们需要怎么做才能让this指向我们指定的对象呢?

    3.1 箭头函数与作用域链

    3.1.1 箭头函数定义: (x,y)=>{return x+y;}

    • 当参数只有一个时,小括号()可以省略: x=>{return x+1;}
    • 当函数体只有一行语句并且该语句结果作为返回值时,{}花括号可以省略: x=>x+1;
    var fun1 = (x)=>{return x + 1;}
    var fun2 = x=>{return x+1;}
    var fun3 = x=>x+1;
    //返回值都是2
    fun1(1);//2
    fun2(1);//2
    fun3(1);//2
    

    3.1.2 箭头函数特性: 箭头函数体内没有自己的this指针

    那么箭头函数体内的this指针将符合作用域链原则,指向作用域链上最近的this

    function fun4() {
      setTimeout(() => {
        console.log(this.id);
      }, 1000);
    }
    var id = 1;
    fun4.call({ id: 2 });//输出为2
    fun4();//输出为1
    

    分析如下:

    • 如果异步操作的回调函数是普通函数,则其this指向window对象。
    • 但箭头函数体内没有this指针,则按作用域链原则,回调函数中的this指针应该为fun4的this指针。
    • fun4作为普通函数,在全局环境调用时this指向window对象,所以 fun4();输出为1
    • function.call作用在调用函数时,将新的对象替换原有对象。则意味着fun4.call时fun4体内的this指针将指向{ id: 2 },所以输出为2

    3.2 闭包和作用域链

    接下来看看闭包中的this指针问题

    //a.js
    function debounce(fn, delay) {  
      var timer = null;
      return function() {    
        var context = this;
        var args = arguments;    
        //定义context和args,通过作用域链达到保存this指针和arguments的作用
        clearTimeout(timer);
        timer = setTimeout(function() {
           //用apply保证回调函数的this指针不会被异步函数重置为window 
           fn.apply(context, args);
        }, delay);
      }
    }
    //等同于
    function debounce(fn, delay) {  
      var timer = null;
      return function() {    
        clearTimeout(timer);
        timer = setTimeout(()=>
          {fn.apply(this, arguments) }, delay);
      }
    }
    
    document.a1 = "2";
    var obj1 = {
        a1:"1",    
        fun1:function(){
            console.log(this);
            console.log(this.a1);
        }
    }
    
    document.addEventListener('scroll', debounce(obj1.fun1, 2000));
    //2秒后输出
    //document
    //2
    
    //b.js
    function debounce(fn, delay) {  
      var timer = null;
      return function() {    
        clearTimeout(timer);
        timer = setTimeout(()=> fn(), delay);
      }
    }
    
    window.a1 = "3";
    document.a1 = "2";
    var obj1 = {
        a1:"1",    
        fun1:function(){
            console.log(this);
            console.log(this.a1);
        }
    }
    
    document.addEventListener('scroll', debounce(obj1.fun1, 2000));
    //2秒后输出
    //window
    //3
    

    3.3 Call,Apply,Bind

    关于这三个函数的对比和作用网上介绍很多,其主要作用为:
    使用that替换函数内部this的指针指向为that。

    function.call(that, arg1, arg2);
    function.apply(that, [arg1, arg2]);
    function.bind(that);
    //如下
    var obj1 = {
      a1:"1",
      fun1:function(){console.log(this.a1)}
    }
    var obj2 = {
      a1:"2"
    }
    obj1.fun1();//1
    obj1.fun1.call(obj2);//2
    var funBind = obj1.fun1.bind(obj2);
    funBind();//2
    
    
  • 相关阅读:
    java多线程编程(一基础概念)
    【转】android程序编译过程
    【转】什么是线程安全和线程不安全
    【转】计算机中时间的表示和存储
    【转】字符编码
    C/C++程序编译流程
    NDK学习笔记-JNI的异常处理与缓存策略
    NDK学习笔记-JNI数据类型和属性方法的访问
    NDK学习笔记-JNI数据类型和属性方法的访问
    NDK学习笔记-JNI开发流程
  • 原文地址:https://www.cnblogs.com/full-stack-engineer/p/9630459.html
Copyright © 2011-2022 走看看