闭包、原型、原型链、预编译解析、作用域、作用域链、继承

110 阅读3分钟

说到作用域和作用域链就要先说一下 变量提升和预编译

变量提升

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是关键字
}