JavaScript 执行机制

Posted 总是不停地吃吃吃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 执行机制相关的知识,希望对你有一定的参考价值。


javascript 执行机制 -- 先编译,再执行

-------------------变量提升 ----------------------

var myname = "zd" =>

var myname = undefined // 声明

myname = "zd" // 赋值

function foo () {
console.log(‘function‘)

} => // 完整的函数声明

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

Javascript经过编译后,会生成两部分内容:执行上下文和可执行代码

执行上下文是Javascript执行一段代码时的运行环境


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


-------------------------------------调用栈 ----------------------------------

1、调用栈是一种用来管理执行上下文的数据结构,符合后进先出的规则
,调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,we call this issue as 栈溢出


2、执行上下文 包括--全局执行上下文, 调用函数时会创建执行上下文, 使用eval函数时会创建执上下文
---需要代码进行编译时,就会创建执行上下文

3、总结:

每调用一个函数,Javascript引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后Javascript
引擎开始执行函数代码

如果在一个函数A中调用了另外一个函数B,那么Javascript引擎会为B函数创建执行上下文,并将B函数
的执行上下文压入栈

当函数执行完毕后,Javascript引擎会将该函数的执行上下文弹出栈


--------------------------词法环境 --------------------------

变量提升,会存在变量覆盖,变量未被销毁,的问题,所以Es6提供let, const关键字,使其Javascript像
其他语言一样拥有块级作用域

1、函数中 全局var定义的变量,在编译阶段 --都会放在 变量环境中(变量会被赋值undefined)

2、let和const声明的变量,在编译阶段,会被存放在词法环境中 (只会有定义,没有进行初始化)

3、在函数的作用域内部,通过 let声明的变量并没有被存放在词法环境中(只有当执行到作用域代码时,才会单独存放在词法环境中,此区域与其他是隔离的,当作用域代码执行完,就会从词法环境栈中弹出)

4、【单个执行上下文】中查找 变量查找过程是 现在词法环境中的独立作用域 从栈顶到栈底 依次查找 , 若在词法环境中没有找到,则去 变量环境中查找


let myname= ‘极客时间‘
{
console.log(myname) //报错,因为作用域中的myname只有定义,没有声明,所以在未声明之前访问变量会报错
let myname= ‘极客邦‘
}

---------------------------作用域链 ------------------------
把通过作用域查找变量的链条称为作用域

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

2、当一段代码使用了一个变量,Javascript引擎首先会在“当前的执行上下文”中查找该变量, 如果 没找到,则会在outer所指向的执行上下文中查找

3、词法作用域--是指作用域是由代码中函数声明的位置来决定的,所以词法作用域就是
静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符
So,词法作用域是在代码阶段就决定好的,和函数是怎么调用的没有关系

4、一个执行上下文可以有一个变量环境,和多个词法环境, 若当前执行上下文未找到变量,则会去outer引用的执行上下文依次查找,直到Outer = Null结束查找


------------------ 闭包 -------------

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


function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}

// we can see , myName和test1是foo函数的闭包

2、闭包若使用不正确,没有合理回收,则会导致内存泄漏

2-1、当引用闭包的函数 是一个全局变量,那么闭包会一直存在直到页面关闭;但如果
这个闭包以后不再使用的话,就会造成内存泄漏

2-2、当引用闭包的函数是一个 局部变量,则等函数销毁后,在下次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()


// 分析代码执行过程

首先判断foo函数是否有闭包--因为bar.printName是全局函数,所以不会引用foo函数内部的变量,所以foo函数不存在闭包

1、全局上下文 : 变量环境有:bar = undefined, foo = function(){}
词法环境有:myName , _printName

2、执行代码:全局上下文 - 词法环境 myName= "极客帮" , _printName = foo()

3、创建foo函数执行上下文,并压入调用栈,其
变量环境: 无
词法环境:myName

继续执行foo函数,foo函数-词法环境 myName = "极客时间" , 查找bar变量

当前词法环境没有-->当前变量环境没有 --> outer词法环境没有 -->outer变量环境有
(找到后,返回找到的值,然后 foo函数执行上下文出栈)

4、此时 _printName = bar.printName

5、创建printName函数上下文,并压入调用栈,其
变量环境无,词法环境无

6、执行printName函数,查找myName变量,当前词法环境没有-->当前变量环境没有-->outer的词法环境有,所以打印值为 “极客帮”, 然后 printName函数上下文 出栈

7、bar.printName() -- 同理 --结果是 “极客帮


-------------------- this机制 -------------------------------

1、执行上下文中 有 变量环境,词法环境,外部环境(outer)还有this , 所以有几种执行上下文,就有几种this

2、全局上下文的this指的是window对象

3、默认情况下,调用一个函数,其执行上下文的this也指向 window对象

function foo(){
console.log(this)
}
foo()

如何改变this指向的对象??

① 使用call函数

bar = { "name" : "test" }

foo.call(bar) // 则foo中的this指向的就是bar

② 通过对象调用函数

A、在全局环境中调用一个函数,函数内部的this指向的是全局变量window

B、通过一个对象来调用其内部的一个方法,该方法的执行上下文 中的this指向对象本身

③ 通过构造函数中设置


function CreateObj(){
this.name = "极客时间"
}
var myObj = new CreateObj()

// this指的是新对象

4、this 设计缺陷 以及解决方案


var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
function bar(){console.log(this)}
bar()
}
}
myObj.showThis()

issue -- 嵌套函数中的this不会从 外层函数中继承 , 此case中,bar() --因为默认调用函数,其this指的是window,所以function bar中 的this打印的不是 myObj,而是window


解决方案:

① 将 myObj的this 保存下来

var myObj = {
name : "极客时间",
showThis: function(){
console.log(this);
var self = this;
function bar(){console.log(self)}
bar()
}
}
myObj.showThis()

② 使用箭头函数

var myObj = {
name : "极客时间",
showThis: function(){
console.log(this);
var bar = () => { console.log(this) }
bar()
}
}
myObj.showThis()

因为箭头函数不会创建单自身的函数执行上下文,所以箭头函数的this 取决于它的外部函数

5、若使用严格模式 , 则 默认的this不会指向window对象,而是undefined

 

 

 

 

 

 

以上是关于JavaScript 执行机制的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 执行机制

彻底弄懂 JavaScript 执行机制

JavaScript执行机制

彻底弄懂 JavaScript 执行机制

JavaScript的执行机制

这一次,彻底弄懂javascript执行机制