说到作用域和作用域链就要先说一下 变量提升和预编译
变量提升
javaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量, 然后在一行一昂的运行 造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升
console.log(a) // undefined
var a = 1
function b(){
console.log(a);
}
b() // 1
上面代码的实际执行步骤是这样的
1、 引擎将var a=1拆解为var a=undeifned和a=1,并将var a=undefined放到最顶端 a=1还在原来的位置
2、 第二步就是执行,因为js引擎一行一行从上往下执行就造成了当前的结果,叫做变量提升
预编译
js语言的特点 1、单线程 2、解释性语言
js运行的三部曲 1、语法分析(扫描依稀是否有语法错误) 2、预编译 3、解释执行
预编译中,变量只有声明提升,函数整体提升,未经声明就使用的变量 归全局所有,即window所有,一切声明的全局变量,全是window的属性
预编译四部曲
1、创建AO(Activation Object)对象(执行期上下文 又:作用域)
2、找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3、将实参值和形参统一
4、在函数体里面找函数声明,值赋予函数体
作用域: 存储了运行期上下文的集合
作用域链:存储的运行期上下文对象的集合,这个集合呈链式链接。我们把这种链式链接叫作用域链
闭包
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏
闭包的作用:
1、实现公有变量。 eg:函数累加器
function add(){
var count = 0;
function demo(){
count ++
console.log(count); // 1,2,3,4
}
return demo
}
let counter = add()
counter()
counter()
counter()
counter()
2、可以做缓存(存储结构)
function add(){
var count = 100;
function a(){
count ++
console.log(count); // 101
}
function b(){
count --
console.log(count); // 100
}
return [a, b]
}
let arr = add()
arr[0]()
arr[1]()
闭包经典问题及其解决办法
function test(){
let arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function(){
console.log(i); // 10 10 10 10 10 10 10 10 10 10
}
}
return arr
}
// 想要输出0 1 2 3 4 5 6 7 8 9有以下两种方法
// 第一种 var、let的区别
function test(){
let arr = []
for (let i = 0; i < 10; i++) {
arr[i] = function(){
console.log(i); // 10 10 10 10 10 10 10 10 10 10
}
}
return arr
}
//第二种 立即执行
function test(){
let arr = []
for (var i = 0; i < 10; i++) {
(function(j){
arr[j] = function(){
console.log(j);
}
}(i))
}
return arr
}
//注册li的点击事件
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
<li>e</li>
<li>f</li>
</ul>
function test(){
let liCollection = document.getElementsByTagName('li')
for (var i = 0; i < liCollection.length; i++) {
(function(j){
liCollection[j].onclick = function(){
console.log(liCollection[j].innerText);
}
}(i))
}
}
test()
原型、原型链
原型 定义:原型是对象的一个属性,它定义了构造函数制造出来的对象的公共祖先。通过该构造方法产生的对象,可以继承原型的属性和方法。
原型也是对象函数也是一个对象,对象不一定是函数。(对象有__proto__属性,函数有prototype属性)
原型的作用: 1数据共享 节约内存内存空间 2.实现继承
Object.prototype是所有对象的最终原型
Object.prototype.proto === null
function Person(){}
function Father(){}
function Grand(){}
let person = new Person()
console.log(person.__proto__ === Person.prototype); // true
let father = new Father()
Person.prototype = father
console.log(Person.prototype.__proto__ === Father.prototype); // true
console.log(person.__proto__.__proto__ === father.__proto__); // false 创建对象之后才修改的原型
console.log(Father.prototype === father.__proto__); // true
console.log(person.__proto__.__proto__ === Person.prototype.__proto__); // false 创建对象之后才修改的原型
let person1 = new Person()
console.log(person1.__proto__.__proto__ === father.__proto__); // true 修改原型之后创建的对象
console.log(person1.__proto__.__proto__ === Person.prototype.__proto__); // true 修改原型之后创建的对象
继承
是什么: 通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承
为什么: 有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费
备注: 最完美的继承方法:圣杯模式
function inherit(Target, Origin){
function F(){}
F.prototype = Origin.prototype
Target.prototype = new F() // 是为了Target和Origin不是同指向一个原型,那样改变Target.prototype也会改变Origin.prototype故使用中间F()过度,既不影响继承也不会修改Target.prototype改变Origin.prototype
Target.prototype.constructor = Target
Target.prototype.uber = Origin.prototype // 可以查看真正继承的是谁 本来是可以用super表达 但是super是关键字
}