【面筋烧烤手册】JavaScript结构专题

Javascript结构专题

1、作用域 / 链

  • 规定变量和函数的可使用范围称作作用域
  • 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。

2、执行上下文和执行栈

  • 执行上下文分为:

  • 全局执行上下文
    创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览器的时候才弹出

  • 函数执行上下文
    每次函数调用时,都会新创建一个函数执行上下文

  • 执行上下文分为创建阶段和执行阶段

  • 创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域

  • 执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象

  • 执行栈:

  • 首先栈特点:先进后出

  • 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行出栈。

  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
    只有浏览器关闭的时候全局执行上下文才会弹出

3、this

  1. 指向不确定,可以动态改变,call/apply 可以改变this的指向(到调用的函数的this里)
  2. 一般指向函数拥有者
  3. 闭包 / 自执行函数(setInterval setTimeout) 指向window对象或上一层对象
  4. dom 回调函数this指向该元素

4、闭包

为什么闭包

  1. 自己作用域外不能访问内部变量,内部可以访问外部
  2. 使闭包要用的局部变量一直存在内存里
  3. 封装私有属性
  4. 匿名空间立即执行,防止全局污染,立即回收

this指向window或上一层闭包拥有者

结构

function xxx(){
	let xxx
	function xxxx(){
		xxxx = xxx //xxx不会被回收
	}
}

((a,b)=>{
	
})(3,4)

(()=>{

})()

5、箭头函数

场景

  • 简化写法的地方
  • 用于本来需要匿名函数的地方
  • 解构和rest的地方
  • 简化回调的地方

解决了什么

  1. 写es6之前的函数很麻烦
  2. 匿名函数实现闭包
  3. ({…, …})=>{ } 要解构的地方
  4. 简化回调函数:a.sort((c, d)=>(c - d))
  5. 传rest参数:
const headAndTail = (head, ...tail) => [head, tail]
headAndTail(1,2,3,4,5) === [1,[2,3,4,5]]

不适用场景

  1. 对象的方法如果有this,如果()=>的话,会指向this指向对象所在作用域(外面或者是window)
  2. addEventListener('click',()=>{this.xxx}) this指向addEventListener外面

注意点

  1. 无原型prototype,所以不能做构造函数,不能new,new关键字内部需要把新对象的_proto_指向函数的prototype
  2. 不能用arguments,因为不是标准函数,在函数体内不存在,可用rest参数代替
  3. 不能用yield,不能做generator函数
  4. 不能用arguments,super,new,target这些指向外层函数作用于
  5. 没有自己的this,无call apply bind绑定

实现尾递归

  1. 实现fib函数
function Fib(n, ac1 = 1, ac2 = 1){
	if(n<=1) return ac2;
	return Fib(n-1, ac2, ac1 + ac2)
}
  1. 实现阶乘函数
function factorial(n, total){
	if(n===1) return total;
	return factorial(n-1, total * n)
}
  1. 柯里化

6、内存泄漏

种类

1. 意外的全局变量

  1. 未声明的变量直接绑到window,如function Fn(){a=...} 等同于 function Fn(){window.a = ...}
  2. function Fn(){this.a = ...} 等同于 function Fn(){window.a = ...}
    使用use stict让this指向undefined

2.被遗忘的计时器或者回调函数

  1. 定时器:a = setInterval(function, 500) 如果function里面有外面的依赖数据,那么定时器不停,依赖的内存就一直不回收
    解决:clearInterval()

  2. btn.addEventListener('click', onclock, false) 一旦不再需要就必须 bin.removeEventListener('click', onclick)不然要一直监听

  3. 脱离DOM的引用,例如:var elements = {btn : document.getElementById(...)}
    后面又remove了这个btn
    但是elements里面还是存在,elements.btn = null;解决

  4. 闭包
    在这里插入图片描述

避免内存泄漏

  1. 及时销毁,null / clearInterval / removeEventlistener
  2. 避免频繁创建过多对象
  3. 避免生命周期长的对象

7、垃圾回收机制的策略

  1. 标记清除法
    垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
  2. 引用计数法
    当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象
  3. 新生代旧生代(V8引擎)
    新生代:To(空闲区)From(判断是否晋升)
    晋升:是否经历过回收?From变成老生代并清除:From变成To然后To和From交换;不存活直接被释放

8、 var let const

  1. var挂到window上
  2. var有变量提升,非use strict可以先使用(默认挂到window)在声明
  3. const let 形成块作用域 var挂到window
  4. 同一作用域 const let不能声明多次 var可以
  5. 暂存死区:
    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)
}

应用场景

  1. 方法重载
    一个类中定义多个同名方法,具有不同参数类型和个数
  • 普通形式(不知道传入的有多少长度和类型)
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++){}
}
  1. 递归调用
  • 实现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)
	}
}
  1. 不定参问题
    arguments.length遍历解决
原文地址:https://www.cnblogs.com/SiriusZHT/p/14365042.html