Javascript结构专题
文章目录
1、作用域 / 链
- 规定变量和函数的可使用范围称作作用域
- 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
2、执行上下文和执行栈
-
执行上下文分为:
-
全局执行上下文
创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出 -
函数执行上下文
每次函数调用时,都会新创建一个函数执行上下文 -
执行上下文分为创建阶段和执行阶段
-
创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域
-
执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
-
执行栈:
-
首先栈特点:先进后出
-
当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行出栈。
-
栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
只有浏览器关闭的时候全局执行上下文才会弹出
3、this
- 指向不确定,可以动态改变,call/apply 可以改变this的指向(到调用的函数的this里)
- 一般指向函数拥有者
- 闭包 / 自执行函数(setInterval setTimeout) 指向window对象或上一层对象
- dom 回调函数this指向该元素
4、闭包
为什么闭包
- 自己作用域外不能访问内部变量,内部可以访问外部
- 使闭包要用的局部变量一直存在内存里
- 封装私有属性
- 匿名空间立即执行,防止全局污染,立即回收
this指向window或上一层闭包拥有者
结构
function xxx(){
let xxx
function xxxx(){
xxxx = xxx //xxx不会被回收
}
}
或
((a,b)=>{
})(3,4)
(()=>{
})()
5、箭头函数
场景
- 简化写法的地方
- 用于本来需要匿名函数的地方
- 解构和rest的地方
- 简化回调的地方
解决了什么
- 写es6之前的函数很麻烦
- 匿名函数实现闭包
- ({…, …})=>{ } 要解构的地方
- 简化回调函数:
a.sort((c, d)=>(c - d))
- 传rest参数:
const headAndTail = (head, ...tail) => [head, tail]
headAndTail(1,2,3,4,5) === [1,[2,3,4,5]]
不适用场景
- 对象的方法如果有this,如果()=>的话,会指向this指向对象所在作用域(外面或者是window)
addEventListener('click',()=>{this.xxx})
this指向addEventListener外面
注意点
- 无原型prototype,所以不能做构造函数,不能new,new关键字内部需要把新对象的_proto_指向函数的prototype
- 不能用arguments,因为不是标准函数,在函数体内不存在,可用rest参数代替
- 不能用yield,不能做generator函数
- 不能用arguments,super,new,target这些指向外层函数作用于
- 没有自己的this,无call apply bind绑定
实现尾递归
- 实现fib函数
function Fib(n, ac1 = 1, ac2 = 1){
if(n<=1) return ac2;
return Fib(n-1, ac2, ac1 + ac2)
}
- 实现阶乘函数
function factorial(n, total){
if(n===1) return total;
return factorial(n-1, total * n)
}
- 柯里化
6、内存泄漏
种类
1. 意外的全局变量
- 未声明的变量直接绑到window,如
function Fn(){a=...}
等同于function Fn(){window.a = ...}
function Fn(){this.a = ...}
等同于function Fn(){window.a = ...}
使用use stict让this指向undefined
2.被遗忘的计时器或者回调函数
-
定时器:a = setInterval(function, 500) 如果function里面有外面的依赖数据,那么定时器不停,依赖的内存就一直不回收
解决:clearInterval() -
btn.addEventListener('click', onclock, false)
一旦不再需要就必须bin.removeEventListener('click', onclick)
不然要一直监听 -
脱离DOM的引用,例如:
var elements = {btn : document.getElementById(...)}
后面又remove了这个btn
但是elements里面还是存在,elements.btn = null;
解决 -
闭包
避免内存泄漏
- 及时销毁,null / clearInterval / removeEventlistener
- 避免频繁创建过多对象
- 避免生命周期长的对象
7、垃圾回收机制的策略
- 标记清除法
垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除 - 引用计数法
当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象 - 新生代旧生代(V8引擎)
新生代:To(空闲区)From(判断是否晋升)
晋升:是否经历过回收?From变成老生代并清除:From变成To然后To和From交换;不存活直接被释放
8、 var let const
- var挂到window上
- var有变量提升,非use strict可以先使用(默认挂到window)在声明
- const let 形成块作用域 var挂到window
- 同一作用域 const let不能声明多次 var可以
- 暂存死区:
let temp = 'global temp'; // 全局作用域下声明的 temp 变量
if (true) {
// if 包含的块级作用域中的 temp 引用,由于还没声明所以错误。
// 这里还涉及到变量绑定的问题,
// 在 ES 6 中如果一个块级作用域中有存在 let 命令,
// 它所声明的变量就 '绑定' 在这个区域,不受外部的影响。
// 这就解释了为什么这里的 temp 没有引用全局的 temp = 'global temp'
temp = 'abc'; // ReferenceError
// 不存在变量声明提示,所以 temp 在该作用域类的声明不会像 ES 3 中一样被放到作用域最开始的地方声明并赋值为 undefined
let temp;
}
// ReferenceError: Cannot access uninitialized variable.
9、Argument对象的实例arguments
作为当前函数的实参
- arguments.length 实参个数
- arguments.callee 引用函数本身
- arguments.caller 引用调用函数的父函数 外层函数
传参
function argTest(a, b, c){
arguments.length //实际传参(不确定)
argTest.length //期望传参 3 (a, b, c)
}
应用场景
- 方法重载
一个类中定义多个同名方法,具有不同参数类型和个数
- 普通形式(不知道传入的有多少长度和类型)
function Test(a, b, c){
if(a b c)
else if
else
}
- arguments实现(因为有arguments并且知道长度 所以可以遍历)
for (var i = 0; i < arguments.length; i++){}
- es6 解构实现(…nums将参数生成了一个新的数组nums)
function test(...nums){
for(var i = 0; i < nums.length; i++){}
}
- 递归调用
- 实现fib函数
普通版:
function Fib(n, ac1 = 1, ac2 = 1){
if(n<=1) return ac2;
return Fib(n-1, ac2, ac1 + ac2)
}
三目运算版
function f(num)
{
if(num<=0)
{
console.log('请输入大于0的正整数');
return ;
}
return num<=2 && num>0 ? 1 : f(num-1)+f(num-2);
}
arguments版:
function f(num)
{
if(num<=0)
{
console.log('请输入大于0的正整数');
return ;
}
return num<=2 && num>0 ? 1 : arguments.callee(num-1)+arguments.callee(num-2);
}
- 实现阶乘函数
普通版:
function factorial(n, total){
if(n===1) return total;
return factorial(n-1, total * n)
}
arguments实现:
function del(num){
if(num<=1){
return 1
}else{
return num*arguments.callee(num-1)
}
}
- 不定参问题
arguments.length
遍历解决