js基础篇-1

109 阅读12分钟

1.执行上下文和执行栈

执行上下文

什么是执行上下文?

Javascript 代码都是在执行上下文中运行的

执行上下文生命周期

1)创建阶段

生成变量对象,建立作用域链,确定this指向

2)执行阶段

变量赋值、函数引用、执行其他代码

image.png

执行上下文的特点:

1)单线程,只在主线程上运行

2)同步执行,从上向下按顺序执行;

3)全局上下文只有一个,也就是window对象;

4)函数每调用一次就会产生一个新的执行上下文环境。

执行栈【调用栈-后进先出】

是一种先进后出的数据结构,用来存储代码运行的所有执行上下文

1.当js引擎,第一次遇到js脚本时,会创建一个全局的执行上下文并压入当前的执行栈

2.当js引擎遇到第一个函数调用,它会为该函数创建一个新的执行上下文并压入栈顶部

3.当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文

4.一旦所有代码执行完毕,JS 引擎从当前栈中移除全局执行上下文

 var a = 1; // 1. 全局上下文环境
      function bar(x) {
        console.log("bar");
        var b = 2;
        fn(x + b); // 3. fn上下文环境
      }
      function fn(c) {
        console.log(c);
      }
      // 2. bar上下文环境
      bar(3);

运行结果:bar 5

2.作用域

定义:可访问变量的集合

优点:隔离变量,不同作用域下同名的变量不会冲突

作用域的类型:

全局作用域函数作用域、ES6新增了块级别作用域

函数作用域

声明在函数内部的变量

块作用域

1)块作用域由{ }包括,if和for语句里面的{ }也属于块作用域
2)在块级作用域中,可通过let和const声明变量,该变量在指定块的作用域外无法被访问

var、let、const的区别

  1. var定义的变量,没有块级的概念,可以跨块访问,可以变量提升

  2. let定义的变量,只能在块作用域里面访问,不能跨块访问,无法变量提升不可重复

  3. const定义常量,使用时候必须初始化,只能在块作用域里面访问,不能修改,没有变量提升,不能重复

var与let的经典案例

//用let声明i,for循环体内部是一个单独的块级作用域,相互独立,不会相互覆盖
var  a = []
      for(let i = 0;i < 10;i++){
        a[i] = function(){
            console.log(i)
        }
      }
      a[0]();

输出结果是:0

//i是var声明的,在全局范围内都有效,全局只有一个变量i,输出的是最后一轮的i值,也就是 10
var  a = []
      for(var i = 0;i < 10;i++){
        a[i] = function(){
            console.log(i)
        }
      }
      a[0]();

输出结果是:10

作用域链

当查找变量的时候,首先会先从当前上下文的变量对象(作用域)中查找,如果没有找到,就会从父级的执行上下文的变量对象中查找,如果还没有找到,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链

3.this

this的5种绑定

  1. 默认绑定(非严格模式下this指向全局对象,严格模式下函数内的this指向undefined
  2. 隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj)
  3. 显示绑定(通过call或者apply方法直接指定this的绑定对象, 如foo.call(obj))
  4. 显示绑定(通过call或者apply方法直接指定this的绑定对象, 如foo.call(obj))
  5. 箭头函数,不能改变箭头函数内的this是由外层作用域决定的

案例1:严格模式的this指向

严格模式,函数内部的this指向undefined

 "use strict";
      var a = 10;
      function foo() {
        console.log("this1", this);
        console.log("this2", window.a);
        console.log("this3", this.a);
      }
      console.log("this4", this);
      foo();

严格模式 image.png

非严格模式 image.png

案例2:let和const

var 改成了 let 或 const,变量是不会被绑定到window上的,所以此时会打印出三个undefined

let a = 10;
      const b = 20;
      function foo() {
        console.log(this.a); // undefined
        console.log(this.b); // undefined
      }
      foo();
      console.log(window.a); // undefined

案例3:函数调用

foo()函数内的this指向的是window,因为是window调用的foo,打印出的this.a是window下的a

var a = 1
function foo () {
  var a = 2
  console.log(this)  // window
  console.log(this.a) // 1
}
foo()

案例4:箭头函数

它的this由外层作用域决定

 var obj = {
        name: "obj",
        foo1: () => {
          console.log(this.name); // window
        },
        foo2: function () {
          console.log(this.name); // obj
          return () => {
            console.log(this.name); // obj
          };
        },
      };
      var name = "window";
      obj.foo1();
      obj.foo2()();

4.闭包

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量局部变量

闭包的垃圾回收:

不合理的使用闭包,会造成内存泄露(就是该内存空间使用完毕之后未被回收)
闭包中引用的变量直到闭包被销毁时才会被垃圾回收

经典案例展示

for(var i = 0; i < 10;i++){
        setTimeout(()=>{
          console.log(i)
        },1000)
      }

输出结果:10

for(let i = 0; i < 10;i++){
        setTimeout(()=>{
          console.log(i)
        },1000)
      }

输出结果:0 1 2 3 4 5 6 7 8 9

5.原型和原型链

原型的作用:给其他对象提供共享属性的对象,函数的实例可以共享原型上的属性和方法

原型链: 当你访问一个对象上的变量的时候,如果该对象内部不存在这个属性,那就会去它的__proto__属性所指向的对象(原型对象)上查找。如果原型对象依旧不存在这个属性,就去原型的的__proto__属性所指向的原型对象上去查找,以此类推,直到找到null,就构成了一个原型链

原型链和作用域的区别:  原型链是查找对象上的属性,作用域链是查找当前上下文中的变量

instanceof 判断对象是否为该构造函数的实例

instanceof 的基本用法,它可以判断一个对象的原型链上是否包含该构造函数的原型,经常用来判断对象是否为该构造函数的实例

console.log(Object instanceof Object); //true
console.log(Function instanceof Function); //true
console.log(Function instanceof Object); //true
console.log(function() {} instanceof Function); //true

instanceof与typeof的区别

1.typeof一般被用于来判断一个变量的类型 typeof可以用来判断number、undefined、string、boolean

特殊情况: typeof null === 'object' typeof [] === 'object' typeof {} === 'object'

2)instanceof判断一个对象的原型链上是否包含该构造函数的原型

6.深拷贝和浅拷贝【数据的类型】

基本类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)。

引用类型: 对象(Object)、数组(Array)、函数(Function)

内存机制:

栈内存(基本类型): 先进栈,后出栈 堆内存(引用类型):引用数据会在栈内存种开辟一个内存空间

JSON.parse(JSON.stringify())

  1. 无法实现深拷贝,无法拷贝 函数、正则、时间格式、原型上的属性和方法等
  2. 可以用递归实现深拷贝

案例1:不支持undefined、函数和循环引用的情况

 const obj = {
            name: "Tom",
            age: 11,
            height: undefined,
            dsc() {
                console.log(`${this.name} is ${this.age} years old!`)
            }
        }
        const cloneObj = JSON.parse(JSON.stringify(obj))
        console.log("cloneObj", cloneObj)

结果: image.png

案例2:递归拷贝

 const obj = {
            name: "Tom",
            age: 11,
            height: undefined,
            dsc() {
                console.log(`${this.name} is ${this.age} years old!`)
            }
        }
function deepClone(obj, cache = new WeakMap()) {
            if (obj === null || typeof obj !== 'object') return obj
            if (obj instanceof Date) return new Date(obj)
            if (obj instanceof RegExp) return new RegExp(obj)

            if (cache.has(obj)) return cache.get(obj) // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环
            let cloneObj = new obj.constructor() // 使用对象所属的构造函数创建一个新对象
            cache.set(obj, cloneObj) // 缓存对象,用于循环引用的情况

            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    cloneObj[key] = deepClone(obj[key], cache) // 递归拷贝
                }
            }
            return cloneObj

        }
        
         obj.a = obj // 循环引用
         const newObj = deepClone(obj)
        console.log("newObj", newObj)

结果: image.png

7.事件轮询机制 【Event Loop】也叫事件的执行机制

js语言是最大的特点就是单线程,也就是说,同时只能做一件事情。所有的任务都需要排队,前一个任务结束,后一个任务才会执行。如果前一个任务耗时过长,后一个任务就不得不一直等待

image.png js执行代码时,看当前执行的代码是同步任务还是异步任务,如果是同步任务直接执行,当遇到异步任务,就会将琦放在异步队列中,当同步任务执行完毕,待会去执行异步任务

所有的任务分为两种:宏任务微任务

graph TD
js任务 --> 同步任务 
js任务 --> 异步任务 -->  宏任务
异步任务 -->  微任务

微任务(优先):promise、async/await、nextTick

宏任务:主代码块、dom事件回调、ajax回调、定时器回调

事件轮询机制执行过程

  1. 代码在执行过程中, 宏任务和微任务放在不同的队列中
  2. 宏任务执行完了,看是否有微任务,如果有就执行,没有直接执行下一个宏任务
  3. 微任务执行结束,会执行宏任务队列中排在第一个的宏任务,依次类推

image.png

async、await事件轮询执行时机

async隐式返回Promise,会产生一个微任务
await后面的代码是在微任务时执行

案例1:setTimeout和Promise
     console.log("script start");
     setTimeout(function () {
        console.log("setTimeout");
      }, 0);
      new Promise((resolve) => {
        console.log("Promise"); // 这里是同步代码
        resolve();
      })
        .then(function () {
          console.log("promise1");
        })
        .then(function () {
          console.log("promise2");
        });
      console.log("script end");

image.png

案例2:async/await
      console.log("script start");
      async function async1() {
        await async2(); // await 隐式返回promise
        console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
      }
      async function async2() {
        console.log("async2 end"); // 这里是同步代码
      }
      async1();
      console.log("script end");

image.png

案例3:综合:
console.log("script start");
async function async1() {
  await async2(); // await 隐式返回promise
  console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
}
async function async2() {
  console.log("async2 end"); // 这里是同步代码
}
async1();
setTimeout(function() {
  console.log("setTimeout");
}, 0);
new Promise(resolve => {
  console.log("Promise"); // 这里是同步代码
  resolve();
})
  .then(function() {
    console.log("promise1");
  })
  .then(function() {
    console.log("promise2");
  }); 
console.log("script end");

// 打印结果:  script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

image.png

vue nextTick优先于微任务执行

Node中的process.nextTick执行顺序早于微任务

从event loop规范探究javaScript异步及浏览器更新渲染时机

Vue异步更新 - nextTick为什么要microtask优先

浏览器与Node的事件循环(Event Loop)有何区别?

8.Promise

Promise 的基本语法

Promise有两个特点

  1. 对象的状态不受外界影响,有三种状态pending(进行中)fulfilled(已成功)rejected(已失败)
  2. 一旦状态改变,就不会再变

1.创建一个Promise

      const p = new Promise((resolve, reject) => {//执行器函数
            // 2.执行异步操作任务
            setTimeout(() => {
                const time = Date.now();
                if (time % 2 == 0) {
                    resolve('success:'+time)
                } else {
                    reject('fail:'+time)
                }

            }, 1000);

        })
        
        const p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve(1)},100)});
        const p2 = Promise.resolve(2);
        const p3 = Promise.reject(3);

链式调用

        p.then(res=>console.log('成功=',res),err=>console.log('失败=',err))

2.Promise.all [返回promises列表中全部执行完的结果]

        const pAll = Promise.all([p1,p2])
        pAll.then(res=>console.log('pAll成功=',res),err=>console.log('pAll失败=',err))

image.png

3.Promise.race [返回promises列表中第一个执行完的结果]

        const pRace = Promise.race([p1,p2])
        pRace.then(res=>console.log('pRace成功=',res),err=>console.log('pRace失败=',err))

image.png

4.retry:当接口失败之后,每隔几秒,再重发一次

/* 
* @param {function} fn - 方法名
* @param {number} delay - 延迟的时间
* @param {number} times - 重发的次数
*/
function retry(fn, delay, times) {
  return new Promise((resolve, reject) => {
    function func() {
      Promise.resolve(fn()).then(res => {
          resolve(res);
        })
        .catch(err => {
          // 接口失败后,判断剩余次数不为0时,继续重发
          if (times !== 0) {
            setTimeout(func, delay);
            times--;
          } else {
            reject(err);
          }
        });
    }
    func();
  });
}

案例:try/catch的异步捕获

        function something(){
            if(Date.now() % 2 === 1){
                console.log("当前是奇数,可以执行任务");
            }else{
                throw new Error('当前时间为偶数,无法执行')
            }
        }
        // 捕获异常
        try { 
            something()
        } catch (err){ 
            console.log(err.stack);
        }

9.定时器

JS提供了一些原生方法来实现延时去执行某一段代码

1.setTimeout和setInterval【两者最短时长为4ms】

setTimeout 固定时长后执行 setInterval 间隔固定时间重复执行

     console.log("先执行这里");
      setTimeout(() => {
        console.log("执行啦");
      },0);

image.png

2.定时器不准的原因

setTimeout和setInterval都是宏任务,根据事件轮询机制,其他任务会阻塞或者延迟js任务的执行

3.setTimeout/setInterval 动画卡顿

不同设备的屏幕刷新频率可能不同,setTimeout/setInterval只能设置固定的时间间隔,设置的时间和屏幕的刷新时间可能不一致

setTimeout/setInterval通过设置一个间隔时间,来不断的改变图像实现动画效果,在不同的设备上可能出现卡顿、抖动等现象

4.requestAnimationFrame 是浏览器专门为动画提供的API

requestAnimationFrame 刷新频率与显示器的刷新频率保持一致,使用该api可以避免setTimeout/setInterval造成的动画卡顿情况 requestAnimationFrame告诉浏览器在下次重绘之前执行传入的回调函数(操纵dom,更新动画的函数)

setTimeout/setInterval与requestAnimationFrame的区别?

requestAnimationFrame代替setInterval定时器

优点:

1.requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘

2.充分利用显示器的刷机机制,节省系统系统资源。

4.requestAnimationFrame启动动画,默认返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

大批量数据优化

在Vue中 使用 Web Worker

案例1:进度条

<div style="width: 0px; height: 12px; line-height: 12px; background: pink">
  0%
 </div>

<script>
      var div = document.querySelector("div");
      div.onclick = function () {
        var timer = requestAnimationFrame(function fn() {
          if (parseInt(div.style.width) < 300) {
            div.style.width = parseInt(div.style.width) + 3 + "px";
            div.innerHTML = parseInt(div.style.width) / 3 + "%";
            timer = requestAnimationFrame(fn);
          } else {
            cancelAnimationFrame(timer);
          }
        });
      };

      // requestAnimationFrame如何停止动画:cancelAnimationFrame()只接受一个参数,
      // requestAnimationFrame 默认返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

      //   requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:
      // 1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
      //   2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
    </script>

image.png

案例2:直线运动

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Document</title>
    <style>
      #e {
        width: 100px;
        height: 100px;
        background: red;
        position: absolute;
        left: 0;
        top: 0;
        zoom: 1;
      }
    </style>
  </head>
  <body>
    <div id="e"></div>
    <script>
      var e = document.getElementById("e");
      var flag = true;
      var left = 0;

      function render() {
        if (flag == true) {
          if (left >= 500) {
            flag = false;
          }
          e.style.left = ` ${left++}px`;
        } else {
          if (left <= 0) {
            flag = true;
          }
          e.style.left = ` ${left--}px`;
        }
      }

      //   方法1
      //   setInterval(function () {
      //     render();
      //   }, 1000 / 60);//1000/60,这是因为大多数屏幕渲染的时间间隔是每秒60帧

      // 方法2
      // requestAnimationFrame效果
      // (function animloop() {
      //     render();
      //     window.requestAnimationFrame(animloop);
      // })();

      // requestAnimationFrame如何停止动画:cancelAnimationFrame()只接受一个参数,
      // requestAnimationFrame 默认返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

      // 方法2,让动画停止
      (function animloop(time) {
        // console.log(time,Date.now())
        render();
        rafId = requestAnimationFrame(animloop);
        //如果left等于50 停止动画
        // if (left == 300) {
        //   cancelAnimationFrame(rafId);
        // }
      })();

      //   requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:
      // 1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
    //   2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
    </script>
  </body>
</html>

10 get和post的区别

误区:

我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。

  1. http协议未规定get和post的长度限制
  2. get的最大长度显示是因为浏览器和web服务器限制了URL的长度
  3. 不同的浏览器和web服务器,限制最大长度不一样

区别:

get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,可以使用缓存 post一般是修改和删除,必须与数据库交互,不能使用缓存

11 事件流

html和javascript交互是通过事件驱动来实现的,比如:onClick等。

事件流:事件流描述的是从页面种接收事件的顺序

事件捕获阶段 事件的目标阶段 事件的冒泡阶段