前端学习-JS执行机制

63 阅读5分钟

前端学习Day2

1.变量提升

  • var声明的全局变量在编译时会提升到作用域的顶部,包含声明和赋值
    •     // 这个console会打印一个undefined而不是报错没有声明
          console.log(a);
          var a = 0;
      
    •     // 上面的代码编译后相当于这段代码
          var a = undefined;
          console.log(a)
          a = 0;
      
  • let和const声明的变量声明会提升到作用域的顶部,但是不会赋值
    •     // 这段代码会报错a没有初始化
          console.log(a)
          let a = 1
      
    •     // 上面的代码编译后会相当于这段代码
          let a
          console.log(a)
          a = 1
      
  • 相同的名称的变量在提升之后,后面的声明会覆盖前面声明
    •     // 这段代码a编译完成后 a是一个数字1 而不能被调用,所以会报错
          a()
          var a = function(){
              console.log(1)
          }
      var a = 1
      
    •     // 上面的代码编译后相当于这段代码
          var a = undefined
          a()
          a = function(){
             console.log(1)
          }
          a = 1
      

2.调用栈

调用栈中存储的是执行上下文

  • 执行上下文
    • image.png
    • 组成
      • 1.变量环境:存储var声明,函数声明
      • 2.词法环境:存储let,const声明,箭头函数
      • 3.执行代码:变量提升完成后执行的代码
    • 分类
      • 1.全局上下文
      • 2.函数上下文
        • 每当一个函数被调用时,都会为该函数创建一个新的执行上下文。
        • 函数调用完成后,这个函数上下文会出栈
        • image.png
      • 3.eval上下文(不用学,避免使用)

3.作用域和作用域链

  • 全局作用域
    • 全局变量挂载在Window对象上
    •     // 这个console会打印出1
          var a = 1
          console.log(window.a)
      
  • 函数作用域
    • 函数内部定义的变量在函数内部生效,函数执行之后会被销毁,不论是var 还是const或者let
  • 块级作用域(ES6新增)
    • 块级作用域指的是{}的内部
    • 在块级作用域中var变量是全局变量,块结束之后仍然存在,let和const变量,块级作用域结束之后会被销毁
  • let 和 const 在创建但是未初始化的期间称为暂时性死区
  • 作用域的关系是在编译阶段就决定好的,和函数怎么调用的没有关系
  • 作用域链就是用来描述作用域关系的
    • 01.png
    • 这张图中bar函数的作用域链是 bar->全局 而不是 bar->foo->全局
    • 变量的寻找会根据作用域链从内而外寻找
  • 面试题目:每隔一秒打印出一个递增的自然数
    •     // 这段代码执行后会打印10个10,原因是setTimeout是异步操作,会在同步代码执行完毕之后再执行,i是全局变量,同步代码全部执行后,所有的i都变成了10,
          for (var i = 0 ; i < 10 ; i ++){
              setTimeout(function(){
                  console.log(i)
              },1000*i)
          } 
      
    •     // 上面的代码编译后相当于这段代码
          // 当开始执行定时任务的时候,i已经变成了10
          var i = 0;
          setTimeout(function () {
              console.log(i)
          }, 1000 * 0)
          setTimeout(function () {
              console.log(i)
          }, 1000 * 1)
          setTimeout(function () {
              console.log(i)
          }, 1000 * 2)
          // ....
          setTimeout(function () {
              console.log(i)
          }, 1000 * 9)
      
    • 解决办法: 将i声明为let
      •   // 改变i为let,得到的代码类似下面的样子,i每次都会重新声明
         {   
              let i = 0;
              setTimeout(function () {
                  console.log(i)
              }, 1000 * 0)
          }
          {   
              let i = 1;
              setTimeout(function () {
                  console.log(i)
              }, 1000 * 1)
          }
          {   
              let i = 2;
              setTimeout(function () {
                  console.log(i)
              }, 1000 * 2)
          }
          {   
              let i = 3;
              setTimeout(function () {
                  console.log(i)
              }, 1000 * 3)
          }
          // ...
          {   
              let i = 9;
              setTimeout(function () {
                  console.log(i)
              }, 1000 * 9)
          }
        

4.闭包

  • 闭包的来源:为了在函数执行完成之后仍然能够获取函数中创建的变量.但是又不希望这个变量可以轻易被污染(全局变量容易被污染)
  • 闭包就使用函数嵌套函数,内部函数可以访问到外部函数的变量.使用变量使用这个函数,这个函数中的变量就因为被引用而不会被销毁,这也造成了可能的内存泄漏问题
  • 闭包实现埋点:
    • 埋点:计算某个页面或者按钮的点击次数
    •     // 这个闭包实现了点击次数的累加,可以被多次调用,而且各个调用之间的num数字独立
          function count() {
              var num = 0;
              return function () {
                  return ++num
              }
          }
      
  • 闭包实现柯里化
    • 柯里化:数学家curry发明了这个理论,减少函数传值个数的方法
    •     // 根据传递正则表达值来给出一个特殊的函数
          function curryingCheck(reg) {
              return function (txt) {
                  return reg.test(txt)
              }
          }
          // 先自定义函数,再调用函数实现业务
          var isPhone = curryingCheck(/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/)
          console.log(isPhone('15810606459'))    // true
      
          var isEmail = curryingCheck(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/)
          console.log(isEmail('wyn@nowcoder.com'))      // false
      

5.this

  • this解决的问题:可以让对象中的属性方法访问到该对象的其他属性.
  • this不同情况的指向
    • 严格模式('use strict')情况下,this指向undefined,非严格模式下指向Window对象
    • 对象中: this指向该对象
    • ES6中箭头函数没有自己的执行上下文,会从自己的父元素继承
    • new 关键字构建的新对象中this都是指向这个对象
    • 嵌套函数中this不继承,指向外层元素
  • 继承this的方法:
    • 使用箭头函数
    • 使用变量存储this
  • 改变this指向的方法
    • call方法: call(this,参数1,参数2,参数3)会直接调用这个方法
      • call的使用场景
        • 对象继承,把自己传给父类,父类进行属性绑定
          •   function superClass () {
                  this.a = 1;
                  this.print = function () {
                      console.log(this.a);
                  }
              }
              function subClass () {
                  superClass.call(this);
                  this.print();
              }   
            
        • 借用方法
    • apply方法: apply(this,[参数1,参数2,参数3])会直接调用这个方法
      • apply
        • 获取数组元素中的max min
        • 合并数组
    • bind方法: bind(this,参数1,参数2,参数3)不会直接调用,需要手动调用