原型-作用域-this
原型和原型链(对象属性的查找)
prototype:每个函数都具有的属性,且只有函数才有的属性。函数的prototype所指向的对象,就是该函数作为构造函数创建的实例的原型对象。
proto:每一个JavaScript对象(除nul)都具有的属性,这个属性会指向该对象的原型。
constructor:每一个原型都具有的属性,该属性指向关联这个原型的构造函数。
每一个JS对象在创建时都会与之关联一个对象,这个对象就是原型,
每一个对象都会从原型”继承”属性,查找对象的属性时,会沿着原型链依次向上查找该属性.
执行上下文(变量的查找)
什么是执行上下文栈,调用栈
JS执行机制: 以代码块为单位 先编译后执行,分为 创建阶段 和 执行阶段.
(”代码块”有三种:全局代码、函数代码、eval代码。)
创建阶段: 会生成两部分 执行上下文 和 可执行代码
执行上下文: 是一段代码时的运行环境,包括执行期间要用到的 变量对象 / 作用域链 / This
执行上下文栈: 用来管理这些执行上下文,是一个 先进后出的 栈结构.
执行上下文栈 其实就是 JavaScript 引擎追踪函数执行的一个机制,和函数执行时的变量查找没什么关系
当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系。
JS执行时首先会遇到全局代码,栈内压入 全局执行上下文.
然后执行到一个函数时,会 从 全局执行上下文 中取出这段函数的代码进行编译
创建该函数的执行上下文,又压入栈内.函数执行完毕,又弹出该执行上下文.
什么是变量提升
JS的执行机制是 以代码块为单位,先编译后执行,分为编译阶段和执行阶段
编译阶段会生成两部分 执行上下文 和 可执行代码
在执行代码之前,其实已经将所有的变量 在 执行上下文 准备好了
使得代码看起来,可以在变量定义之前就访问到该变量
变量提升是JS的一个重要设计缺陷, 在ES6中 用 块级作用域 配合 const let 避免这种缺陷。
作用域: 函数和变量的可被访问的范围,生效范围 (全局作用域 函数作用域 块级作用域)
JS的设计者一开始也没想到JS会这么火,没有设计的那么复杂。
没想到简单恰恰导致了JS火起来,而火起来后又需要更多的规范
这就像开发一个项目一样,功能简单 意味着 领域专注 快速开发 成本降低
缺陷: 变量污染 变量覆盖
const/let解决变量提升(执行上下文的变量环境 和 词法环境)
执行上下文中 分为 变量环境 和 词法环境
变量环境 储存var
声明的变量
词法环境 是一个栈,以块级作用域为 单位储存const
/let
声明的变量.类似 执行上下文栈 的使用机制
编译阶段:
函数作用域内所有 var声明的变量 创建并初始化 在 变量环境__,
函数作用域内const
/let
创建不初始化 在 __词法环境 栈底,
这里其实是 块级作用域(这里相当于函数作用域内所有,且剔除内部其他块级作用域内的变量)
执行阶段:
执行到块级作用域,块级作用域中的 所有 const
/let
变量立即 创建,并压入 词法环境 栈底,
此时变量已被创建,但如果在声明之前调用 const
/let
变量,由于未 初始化,JS引擎报错 暂时性死区
此时变量的查找顺序为,先从 词法环境 栈顶找到栈底,再找 变量环境
var
的创建和初始化被提升,赋值不会被提升。let
的创建被提升,初始化和赋值不会被提升。function
的创建、初始化和赋值均会被提升
作用域和作用域链(变量的查找)
和直觉不同,变量的查找 并非 沿着执行上下文栈进行
function bar() {
console.log(myName)
}
function foo() {
var myName = "lzy内"
bar()
}
var myName = "lzy外"
foo() // "lzy 外"
作用域
其实就是 变量对象, 代表 函数和变量的可被访问的范围,生效范围.
变量存在于哪个 作用域 或 属于哪个变量对象,就可以在这个变量对象内被访问.
作用域链
是JS查找变量的一套规则,本质上是由多个执行上下文的变量对象构成的链表,
链表的层级由 词法作用域 决定,查找变量时,会沿着作用域链查找.
作用域链1 (函数创建时产生)
外层代码段编译时, 作为一个对象, 会产生一个内部属性 [[scope]],
并根据 词法作用域,从内到外层层将父代码段的 变量对象 保存其中.
词法作用域
也是 __静态作用域__,JS的 作用域 由函数定义的位置决定,而不是执行时的 调用栈决定.
因为函数他所能引用的外部变量[[scope]],在函数创建的时候就已经决定了,而不是函数执行的时候
作用域链2 (函数调用时产生)
(进入自己的创建阶段,创建自己的执行上下文,压入执行上下文栈)
复制自己 [[scope]]属性 创建作用域链2, 创建自己的AO并初始化, 压入作用域链顶端.
闭包
理解闭包的产生比理解闭包的概念更重要,
闭包的产生
根据词法作用域规则,函数的作用域链由函数声明位置决定,声明位置在内部的函数总是可以访问外部函数的变量,
即使内部函数的调用位置,不在其声明的外层函数内,甚至外部函数已经执行结束,
但内部函数 引用的外部函数变量依旧保存在内存中,这些变量的集合称为闭包.
闭包
函数与其引用的外部函数词法环境的集合.(函数,自己内部的变量,外部词法环境,可访问的外部变量)
闭包存在两种情况,一种是 内部函数正常在内部执行时,外部词法环境变量均存在,只要函数执行就存在闭包
一种是 内部函数执行时外部函数执行上下文已被销毁,
当外部函数已被销毁,内部函数 引用了的外部变量依旧会被保存,
而 未被引用的外部变量 会通过 tree-shaking的方式 被销毁。
__多个内部函数共享共同一个闭包__,
当外部函数有多个内部函数,只要任意内部函数使用了外部函数的变量,所有内部函数均持有闭包,且共享同一个闭包.
返回内部函数时,JS引擎会提前分析闭包内部函数的词法环境,有引用的外部变量都不会被gc回收.
This
This机制: 使用对象来调用其某个方法属性,该方法内部的This,指向的是对象
This机制的意义: 为了在对象内部的方法中使用对象内部的属性(然后用到原型链)
原型链机制的意义: 查找对象的属性时,会沿着原型链依次向上查找该属性.
缺陷: 嵌套函数的This不会从外部函数继承(箭头函数解决)
普通函数的This默认指向全局对象window
各种开源库中 函数返回函数 经常会先bind(This)一下,就是因为这个缺陷
原型链 与 作用域链 机制类似,却不是一套