Js执行过程:
一段js代码执行之前需要被js引擎编译,编译之后才会进入执行阶段。 js代码经过编译之后会生成两部分,执行上下文和可执行代码。
执行上下文:js执行一段代码的一个运行环境。比如调用一个函数,就会进入函数的执行上下文,确定函数执行期间用到的变量 this 对象以及函数等。
在执行上下文中,存在一个上下文变量环境对象,里面存放了变量提升的内容。在编译的过程中会把var声明的变量以及function声明的函数存放在变量环境对象中,如果存在两个相同的函数声明则会覆盖掉之前定义的函数,在执行的时候,js引擎,会去变量环境对象中,查找自定义的变量和函数。
js的调用栈
js的调用栈:js会将执行上下文压入栈,将这种管理执行上下文的栈叫做调用栈。
调用栈是一种管理执行上下文的数据结构。 调用栈是有大小的,当入栈的执行上下文数量超过一定大小的时候就会报错,栈溢出。
特别是写递归代码的时候很容易造成栈溢出。
function division(a,b) {
return division(a,b)
}
console.log(division(1,2))
1.js引擎每调用一个函数会为这个函数创造一个执行上下文,且会把这个执行上下文压入栈底
2.如果一个函数A调用了函数B,js会为函数b创建执行上下文并且会将执行上下文又压入栈底
3.当执行完一个函数之后,会将这个函数的执行上下文弹出栈
4.当分配的栈空间被沾满的时候就会报错说栈溢出,一般会出现在递归的时候。
console.trace() 来输出当前的函数调用关系
作用域
ES6之前只有函数作用域和全局作用域。 ES6提出了块级作用域。
- 全局作用域中的对象在代码中的任何地方都可以访问,变量生命周期伴随页面的生命周期。
- 函数的作用域只在函数内部访问,函数执行完就会销毁。
变量提升带来的问题:
- 在不被察觉的情况下覆盖变量的值(意外的全局变量
var myname = "test "
function showName(){
console.log(myname);
if(0){
var myname = " test123 " // 被提升到函数内部了
}
console.log(myname);
}
showName() // 结果是 undefined
- 本应该销毁的变量没有被销毁
function foo(){
for (var i = 0; i < 7; i++) { }// var将i提升到了函数作用域
console.log(i); // for循环后i还是存在
}
foo() // 7
ES6是如何在函数作用域的基础上实现let const的块级作用域的?
- 词法作用域 js编译的过程中,var声明的变量会存放在执行上下文环境变量对象中,let声明的变量会放在词法作用域中。
词法环境中也会有一个栈,不同块作用域的变量会依次存入词法环境的栈中,当作用域块执行完毕,其内部定义的变量就会从词法环境的栈顶弹出。
暂时性死区:
es6规定,区块中声明了let const,对这个区块let const声明变量之前使用变量就会报错“不能使用未初始化前使用这个变量”。
当运行到块级作用域的时候会先预编译,然后let const声明的变量为undefined,在赋值之前使用let const定义的变量会报错,虽然该变量已经在词法作用域中了,但是没有赋值所以不能被使用。
作用域链
每个执行上下文的变量环境中都包含了一个外部引用,用来指向外部的执行上下文。
当一段代码使用了一个变量,js引擎会在当前执行上下文中查找这个变量,找不到就会在指向的外部引用的执行上下文中去查找,直到找到为止,一直找不到就返回这个变量未定义。
这个查找的链条就叫做作用域链。执行上下文的外部引用是由词法作用域来决定的。
词法作用域
词法作用域是指,作用域是由代码中函数声明的位置决定的,词法作用域是代码阶段就定义好的,与代码的调用没有关系。
此图片的作用域链式 foo函数-》bar函数-》main函数-》全局作用域
闭包
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。
通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = " 极客时间 "
return bar.printName
}
let myName = " 极客邦 "
let _printName = foo()
_printName()
bar.printName()
This
每个执行上下文中都有一个 this。执行上下文主要分为三种:全局执行上下文、函数执行上下文和 eval 执行上下文,
所以对应的 this 也只有这三种。
全局执行上下文中的 this、函数中的 this 和eval 执行上下文中的this
- 全局执行上下文中的 this 全局执行上下文中的 this是指向window对象的。这也是 this和作用域链的唯一交点,作用域链的最底端包含了window 对象。
- 函数执行上下文中的 this
- 当函数作为对象的方法调用时,函数中的 this 就是该对象;
- 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;
- 嵌套函数中的 this 不会继承外层函数的 this 值。
- 箭头函数并不会创建其自身的执行上下文,所以箭头函数中的this取决于它的外部函数。
- new的时候this指向的是new函数左边赋值的变量。
- 通过函数的 call apply bind 方法设置