JavaScript变量提升深入浅出

2018-11-10  本文已影响0人  3ef698de5362

沉淀之前

本人从事前端开发工作已经一年多了,在一家国企的信息技术部门工作,自己认为对前端技术有一定了解了,于是就想跳出围城看看外面的大千世界,找一家互联网公司开挑战一下自我。面试了头条,百度,美团这些一线互联网公司,面完之后才知道自己的是井底的蛙儿,不知道天地的广袤无垠。为了更好的督促自己,让自己的学习有迹可循,同时也能更好的和各位兄弟姐妹们交流就写一写博客。这是我第一次在简书上写,写的不好还请多批评指正。

1.JavaScript基本概念回顾

[1] 首先JavaScript是一种弱类型、动态的、解释型语言。
弱类型:类型检查不严格,偏向于容忍隐式类型转换。
强类型:类型检查严格,偏向于不容忍隐式类型转换。
动态类型:运行的时候执行类型检查
静态类型:编译的时候就知道每个变量的类型
解释型:程序不需要编译,程序在运行的时候才翻译成机器语言,每执行一次都要翻译一次,因此效率比较低,但跨平台性好。
编译型:程序在执行之前需要一个专门的翻译过程,把程序编译为机器语言的文件,运行时直接使用编译的结果就好了。
编程语言:具有逻辑性和行为能力,这是主动的。
脚本语言:不需要编译,可以直接使用,由解释器来负责解释。

[2] 执行环境及作用域
执行环境定义了变量或函数有权访问的的其他数据,决定了他们各自的行为。全局执行环境是最外围的一个执行环境,根据ECMAscript所实现的宿主环境不同,表示执行环境的对象也不一样。在WEB浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是window对象的属性和方法创建的。每个函数都有自己的执行环境。当执行流入一个函数时,函数的环境就会被推入一个栈中,而在函数执行后,栈将其弹出,把控制权返回给之前的执行环境。

[3] JavaScript没有块级作用域
上面说道JavaScript中只有全局作用域和局部作用域(函数作用域)。在其他类C的语言中,由花括号封闭的代码块都有自己的作用域。JavaScript没有块级作用域,所以if 和 for等语句定义的变量都是会存在于外部的执行环境中的。

2.变量提升深入浅出

什么是变量提升呢?
变量提升包含两部分:变量提升和函数提升。
变量提升是把变量提升提到函数顶部。需要说明的是,变量提升只是提升变量的声明,并不会把赋值也提升上来。
函数提升是把整个函数提到前面去。函数声明形式能被提升,函数表达式不能。
function foo(){console.log(1)} //函数声明
var foo = function(){ console.log(1) } //函数表达式不能被提升,
[1] JavaScript 代码解析原则
首先JavaScript引擎在读取js代码时会进行两个步骤,第一个步骤是解释,第二个步骤是执行。解释就是先通篇扫描所有的Js代码,然后把所有声明提升到顶端,第二步是执行。第一步的的编译就包含变量提升。
[2] 声明变量
使用var声明的变量会自动添加到最近的环境中。在函数内部,最接近的环境就是函数内部的局部环境(with语句就不说了)。如果初始化变量时没有使用var声明,该变量就会自动被添加到全局环境中。示例如下:

function add(num1, num2){
    var sum = num1+ num2;
    return sum;
}
var result = add(10, 20)
alert(sum)

编译时过程如下:这个主要是在函数内部的局部作用域的变量提升

function add(num1, num2){
    var sum; //先提升
    sum = num1 + num2; //后赋值
    return sum
}
var result;
result = add(10, 20)
alert(sum)  //由于 sum是函数内的局部变量,函数外部访问不到,因此会导致错误。

如果上述示例做如下修改

function add(num1, num2){
    sum = num1 + num2  // 初始化变量时没有使用var声明,该变量就会自动添加到全局环境中。
    return sum;
}
var result = add(10, 20); // 30
alert(sum); // 30  全局变量可以访问到

[3] 查询标识符
在查询某个环境中为了读取或者写入而引用一个标识符是,必须通过搜索来确定该标识符实际代表什么。当搜索过程从作用域的前端开始,向上逐级查询与给定匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域向上搜索。搜索过程将一直追溯到全局环境的变量对象。查询标识符过程示例如下:

var color = "blue";                     var getColor = function(){
function getColor(){                        return color;
    return color;           编译         }
}                                        var color;
alert(getColor());                       color = blue;   alert(getColor())

搜索过程:return color -> getColor -> color -> window 结果为blue

3. 变量提升实战

[1] 牛刀小试
1.函数声明的提升优先于变量声明的提升

 var scope = 'global';
function f(){
    console.log(scope);
    var scope = 'local';
    console.log(scope);
}

编译后

var f = function(){
      var scope;
      console.log(scope);  //在函数的局部作用域中,定义了没有赋值,输出undefined
      scope  = 'local';
      console.log(scope)  //赋值后,输出local
}
var scope;
scope = 'global'

(2)局部变量定义时,通过var声明和不用var声明直接使用带来的问题

!function(){
    var t = 1;
    function hello(){
      t = 2;
      console.log(t);
  }
  hello(); // 2
  console.log(t); // 2
}
!function(){
  var t=1;
  function hello(){
    var t=2;
    console.log(t);
  }
  hello(); // 2
  console.log(t); // 1
}();

[2] 直击面试

// 头条+百度面试题
function Foo(){
    getName = function(){alert(1)}; 
    return this;
}
Foo.getName = function(){ alert(2); };
Foo.property.getName = function(){ alert(3); };
var getName = function() { alert(4); };
function getName() { alert(5); };

Foo.getName(); // ?
getName(); // ?
Foo().getName(); // ?
getName(); // ?
new Foo.getName(); // ?
new Foo().getName(); // ?
new new Foo().getName(); // ?

这里牵扯到一些JavaScript面向对象设计原型链继承的问题,今天先不做介绍,会有一章专门介绍JavaScript面向对象的程序设计。js编译过程如下:

var Foo = function(){
    getName = function(){ alert(1); }; //注意这里没有var声明会添加到全局
    return this
}
var getName = function(){ alert(5);};
var getName;
Foo.getName = function(){ alert(2); };
Foo.property.getName = function(){ alert(3);};
getName = function(){ alert(4); }

Foo.getName(); // alert(2)
getName(); // == this.getName()  所以是  alert(4)
Foo().getName(); // alert(1)
getName(); // alert(1)
new Foo.getName(); // alert(2)
new Foo().getName(); // alert(3)
new new Foo().getName(); // ?

解题开始:
第一题,第二题看到解析后的变量提升就可以直接得出答案了
(1)Foo.getName()

Foo.getName = function(){ alert(2); };

(2)getName()

全局getName函数:getName = function() { alert(4) }

(3)Foo().getName();

Foo()这样调用就是把构造函数当成普通函数在调用
里面的this指向了window对象
所以里面的return this,就相当于把window对象返回了
var res = Foo(); res就是接受的window对象
getName = function(){alert(1);};
这个代码是给全局getName重新赋值了一个function!所以调用的时候是1了
Foo().getName() == window.getName() == function(){alert(1);};

(4)getName();

由于上一步代码已经将getName值改变,所以getName =  function (){alert(1)}

(5)new Foo.getName();

new 构造函数();
下面的代码就是把Foo.getName整体当做了一个构造函数
使用new关键字就做了三件事情
var obj = {}; obj.__proto__ = Foo.getName.prototype; Foo.getName.call(obj);
所以调用构造函数就是调用Foo.getName里面的alert语句就会被执行
Foo.getName = function(){alert(2);}

(6)new Foo().getName();

上面的代码可以拆解成
var obj = new Foo();
obj.getName();
由于obj中病没有getName属性,所以就要到原型Foo.prototype中去查找
Foo.prototype.getName = function(){alert(3);};

(7)new new Foo().getName()

上面的代码可以拆解成
var obj = new Foo();
var func = obj.getName
同(6)题由于obj中没有getName属性,所以他将在原型Foo.prototype中查找
Foo.prototype.getName = function(){ alert(3); };
所以 func = function(){ alert(3); };
所以 new func()输出为 alert(3);

小结

变量提升现阶段总结到此结束,第一次写博客写的不好,还望大家多多批评指正。里面还有很多概念没有详细讲解例如函数闭包,原型链的继承会在后面的博客中继续总结,谢谢大家。

上一篇下一篇

猜你喜欢

热点阅读