浏览器学习笔记-07 js基础 js 基础

变量提升

先通过下面这段代码,来看看什么是 JavaScript 中的声明和赋值。  

var myname = '极客时间'

这段代码你可以把它看成是两行代码组成的:

var myname //声明部分
myname = '极客时间' //赋值部分

所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的 “行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。

函数和变量在执行之前都提升到了代码开头。函数声明优先级高于变量声明,相同名称的声明,函数会覆盖变量声明,后面的函数声明会覆盖前面的函数声明。注意:不带var 关键字的是一个全局对象的属性,不是声明全局变量,建议使用 let const

js 执行过程

一段js代码 --> 编译阶段 --> 执行阶段

浏览器学习笔记-07 js基础
js 基础

  • JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。
  • 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为 undefined;在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数。
  • 如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的

栈溢出

我们就来明确下,哪些情况下代码才算是“一段”代码,才会在执行之前就进行编译并创建执行上下文。一般说来,有这么三种情况

  • 当JavaScript执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
  • 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
  • 当使用eval函数的时候,eval的代码也会被编译,并创建执行上下文。

每调用一个函数,JavaScript引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后JavaScript引擎开始执行函数代码。
如果在一个函数A中调用了另外一个函数B,那么JavaScript引擎会为B函数创建执行上下文,并将B函数的执行上下文压入栈顶。
当前函数执行完毕后,JavaScript引擎会将该函数的执行上下文弹出栈。
当分配的调用栈空间被占满时,会引发“堆栈溢出”问题。

var a = 2
function add(b,c){
  return b+c
}
function addAll(b,c){
    var d = 10
    var result = add(b,c)
    return  a+result+d
}
addAll(3,6)

浏览器学习笔记-07 js基础
js 基础
浏览器学习笔记-07 js基础
js 基础
浏览器学习笔记-07 js基础
js 基础
浏览器学习笔记-07 js基础
js 基础
浏览器学习笔记-07 js基础
js 基础
浏览器学习笔记-07 js基础
js 基础

let,const

正是由于 JavaScript 存在变量提升这种特性,从而导致了很多与直觉不符的代码,这也是 JavaScript 的一个重要设计缺陷。

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

在 ES6 之前,ES 的作用域只有两种:全局作用域和函数作用域。

ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有了块级作用域。

浏览器学习笔记-07 js基础
js 基础

沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找。

作用链和闭包

ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念。

其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。

当一段代码使用了一个变量时,JavaScript 引擎首先会在 “当前的执行上下文” 中查找该变量。

浏览器学习笔记-07 js基础
js 基础

从图中可以看出,bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链。
在 JavaScript 执行过程中,其作用域链是由词法作用域决定的

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。词法作用域和函数声明的位置相关,和变量声明无关。

块级作用域中的变量查找

在编写代码的时候,如果你使用了一个在当前作用域中不存在的变量,这时 JavaScript 引擎就需要按照作用域链在上层作用域中查找该变量。

闭包

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

通常,如果引用闭包的函数是一个全局函数,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄露。

如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。

所以在使用闭包的时候,你要尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。

this


var bar = {
    myName:"time.geekbang.com",
    printName: function () {
        console.log(this.myName)
    }
}
function foo() {
    let myName = "极客时间"
    return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()

作用域链和 this 是两套不同的系统,它们之间基本没太多联系

浏览器学习笔记-07 js基础
js 基础

执行上下文主要分为三种——全局执行上下文、函数执行上下文和 eval 执行上下文,所以对应的 this 也只有这三种——全局执行上下文中的 this、函数中的 this 和 eval 中的 this。

  1. 全局执行上下文中的 this
    window 对象,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。

  2. 函数中的this

  • 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window;
  • 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
  1. eval 中的this
    只在被直接调用时,this指向才是当前作用域。