JavaScript之this和执行环境

温故而知新,重新记录学习js执行环境和this,以及bind,apply,call手动实现,方便以后巩固。

如何理解JS中得执行环境(上下文)?

  • 执行环境(Execution context,EC)执行上下文,定义了变量或函数有权访问的数据集合,并决定他们各自的行为。js为每个执行环境关联一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
注意:【在函数执行环境中,VO 是不能直接访问的,则将其活动对象AO(activation object)作为变量对象。AO 是在进入函数的执行环境时创建的,并为该对象初始化一个 arguments 对象(这个对象在全局环境是不存在的!)】。
  • 执行环境分为三种:全局执行环境,函数执行环境,evel()执行环境。
  • 执行环境由变量对象,[[Scope]]属性(执行作用域链),this指针(用来指向一个环境对象)
  • 活动对象AO = 变量对象VO + 函数的parameters + arguments特殊对象。只有全局变量的变量对象允许通过 VO 的属性名称间接访问(全局对象:
    1. Web 浏览器中,全局执行环境就是 window 对象
    2. nodejs 的中,全局执行环境就是global 对象。
  • 执行环境分析:
    1. 全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的【代码载入浏览器时,全局环境被创建,当我们关闭网页或者浏览器时全局执行环境才被销毁】。
    2. js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个执行栈中。而在函数执行之后,栈将该函数的变量对象弹出,该环境就被销毁,保存在其中的所有变量和函数定义也随之销毁,再把控制权交给之前的执行环境变量对象。

说说JS中的作用域?

  • [[Scope]]作用域: 变量的作用域分为两种: 全局变量:最外层函数定义的变量,拥有全局作用域,任何内部函数都可以访问。 局部变量:局部作用域一般只在固定代码片段内可以访问到,而对于函数外部是无法访问。函数内部声明变量要用var,否则会被认为是全局变量

  • 作用域链[[Scopr Chain]]:根据内部函数可以访问外部函数变量的这种机制,用链式查找可以决定哪些数据内别内部函数访问。

  • 分析: 当某个函数第一次被调用时,就会创建一个执行环境EC以及相应的作用域链,并把作用域链赋值给以一个特殊的内部属性[Scope]。然后使用this,arguments和其他命名的参数来初始化函数的活动对象AO,当前执行环境的变量对象始终在作用域链的第0位。


JS中什么情况下会出现变量提升?

  • JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)
  • 注意事项:
  1. 如果函数和变量声明时同一个变量,最终会为函数
        function a() {
            console.log(1);
        }
        var a;
        console.log(a);//答案为函数。
        //如果把var a 变成 var a=10,最终输出10;
复制代码
  1. 函数和变量相比,声明函数才会被优先提升
        console.log(a);//输出函数
        function a() {
            console.log(1);
        }
        var a=10;
        console.log(a);
复制代码
  1. 声明式函数才能整个函数提升,赋值式函数是不能整个函数提升
    function a(){
        b();//可以执行,整个函数提升了
        c();//不可以执行,因为c提升了,但是值为undefined。
        function b(){
            console.log('b');
        }
        var c=function(){
            console.log('c');
        }
    }
    a();
复制代码
  1. ES5中的var 和 function 的声明都存在变量提升,ES6中的 let 、 const 则不存在有变量提升。

实战:以下代码输出什么?为什么?

        var name = '梦回';
        (function () {
            //相当于把var name=undefined提升到这里
            if (typeof name === 'undefined') {
                var name = '前端';
                console.log('hello' + name);
            } else {
                console.log('World' + name);
            }
        })()
        //输出'hello前端'
        //因为在匿名函数的作用域中,name被提升到匿名函数内部的顶端,
        //在函数作用域中已经有name,就不会去获取全局的name
        //但是此时的值为undefined。
复制代码

如何理解this,简述JS中的this指向?

  • this就是函数运行时所在的环境对象。this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象
    1. 纯粹的函数调用。在js的严格版中this指向不是window,是undefined。
                var x = 1;
                function test() {
                console.log(this.x);
                }
                test();  // 1 this指向window
    复制代码
    1. 作为对象方法的调用。如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象
                function test() {
                console.log(this.x);
                }
                var obj = {};
                obj.x = 1;
                obj.m = test;
                obj.m(); // 1 this指向obj
    复制代码
    1. 作为构造函数调用,this表示new生成的对象,会进行如下操作:
    • 创建一个空的简单JavaScript对象(即{})
    • 链接该对象(即设置该对象的构造函数)到另一个对象
    • 将步骤1新创建的对象作为this的上下文
    • 如果该函数没有返回对象,则返回this
                function test() {
                 this.x = 1;
                }
                var obj = new test();
                obj.x // 1 this指向obj
                
                //特殊的例子:
                function test1()  
                {  
                    this.x = 1;  
                    return {};
                }
                var a = new test1();  
                console.log(a.x); //undefined
                //如果返回值不是一个对象那么this还是指向函数的实例。
    
    复制代码
    1. apply 调用。是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数。为空表示window
                var x = 0;
                function test() {
                 console.log(this.x);
                }
                var obj = {};
                obj.x = 1;
                obj.m = test;
                obj.m.apply() // 0 this指向window
    复制代码

参考 参考

JS中如何改变this指向?

  • new关键字改变this指向
  • 使用bind,call,apply。call和apply与bind区别,call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。而call和apply的区别是第二个参数的区别,apply是第二个必须是数组,call是除了第一个参数以外还可以添加多个参数

实现call,apply,bind函数?

Function.prototype.myBind = function() {
    var _this = this;
    var context = [].shift.call(arguments);// 保存需要绑定的this上下文
    var args = [].slice.call(arguments); //剩下参数转为数组
    console.log(_this, context, args);
    return function() {
        return _this.apply(context, [].concat.call(args, [].slice.call(arguments)));
    }
};

/**
 * 每个函数都可以调用call方法,来改变当前这个函数执行的this关键字,并且支持传入参数
 */
Function.prototype.myCall = function(context) {
    //第一个参数为调用call方法的函数中的this指向
    var context = context || global;
    //将this赋给context的fn属性
    context.fn = this;//此处this是指调用myCall的function

    var arr = [];
    for (var i=1,len=arguments.length;i<len;i++) {
        arr.push("arguments[" + i + "]");
    }
    //执行这个函数,并返回结果
    var result = eval("context.fn(" + arr.toString() + ")");
    //将this指向销毁
    delete context.fn;
    return result;
}

/**
 * apply函数传入的是this指向和参数数组
 */
Function.prototype.myApply = function(context, arr) {
    var context = context || global;
    context.fn = this;
    var result;
    if (!arr) {
        result = context.fn(); //直接执行
    } else {
        var args = [];
        for (var i=0,len=arr.length;i<len;i++) {
            args.push("arr[" + i + "]");
        }
        result = eval("context.fn([" + args.toString() + "])");
    }
    //将this指向销毁
    delete context.fn;
    return result;
}
复制代码

参考 参考 参考

ES6新增的箭头函数能改变this指向么?箭头函数使用场合?

  • 不能。函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。绑定定义时所在的作用域,而不是指向运行时所在的作用域。对象中使用箭头函数,该this为window(最近的作用域对象),对象不构成单独的作用域

  • 使用场合:箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中。

  • 不使用场景:

    1. 箭头函数不适合定义对象的方法(对象字面量方法、对象原型方法、构造器方法),因为箭头函数没有自己的 this,其内部的 this 指向的是外层作用域的 this。
    2. 箭头函数不适合定义结合动态上下文的回调函数(事件绑定函数),因为箭头函数在声明的时候会绑定静态上下文

参考


练习:

var name ="梦回";
var obj={
    name:"前端",
    sayName:function(){
        console.log(this.name);
    },
    callback:function(){
        var that=this;
        return function(){
            var sayName=that.sayName;
            that.sayName();//前端,这边调用了局部变量的that,而that保存obj对象。这个方法就是输出obj对象的name
            sayName()//梦回,运行时该this为window,
        };//
    }
}
obj.callback()();//一个括号调用,生成了闭包。第二个括号调用,闭包的调用,就会读取函数内部的变量。

//使用let,声明name,是就不是window的属性。所以输出得时候结果会变成sayName(),会输出空字符。原因是name是window自带属性且为空字符。换成其他变量就会变成undefined
复制代码
分类:
前端
标签: