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