六万字、42个知识点、超长篇幅助你了解 JS(二)JS 面试大全

683 阅读21分钟

此篇文章 Markdown 字数 六万六千 多字,HTML 字数 五万五千 字,全是知识点...

掘金...六万字发不了,只能拆开,拆成三篇文章。

六万字、42个知识点、超长篇幅助你了解 JS(一)JS 面试大全

六万字、42个知识点、超长篇幅助你了解 JS(二)JS 面试大全

六万字、42个知识点、超长篇幅助你了解 JS(三)JS 面试大全

十五、说下 JSON.stringifyJSON.parse

1、JSON.stringify

定义:将一个 JavaScript 对象或值转换为 JSON 字符串。 参数:有三个参数

JSON.stringify(value[, replacer [, space]])
  1. replacer replacer 参数可以是一个函数或者一个数组。 作为函数,它有两个参数,键( key )和值( value ),它们都会被序列化。 replacer 是一个数组,数组的值代表将被序列化成 JSON 字符串的属性名。
  2. space space 参数用来控制结果字符串里面的间距。 如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格; 如果是一个字符串,则每一级别会比上一级别多缩进该字符串。

2、JSON.parse

定义:用来解析 JSON 字符串。 参数:有两个参数

JSON.parse(text[, reviver])
  1. reviver 转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值。

特性

  1. 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。
  2. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
  3. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
  4. undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)
  5. 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
  6. 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
  7. Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
  8. NaNInfinity 格式的数值及 null 都会被当做 null
  9. 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

十六、=====Object.is()

1、区别

  • == 两边值类型不同的时候,先进行类型转换,在比较
  • === 不进行类型转换,直接值比较
  • Object.is(val1, val2) 判断两个值是否为同一值

2、== 类型转换是怎么转换的?

  1. 如果类型不同,进行类型转换
  2. 判断比较的是否是 null 或者是 undefined,如果是,返回 true
  3. 判断类型是否为 string 或者 number,如果是,将 string 转换为 number
  4. 判断其中一方是否为 boolean,如果是,将其中一方转为 number 在进行判断
  5. 判断其中一方是否为 object,且另外一方是 stringnumbersymbol,如果是,将 object 转为原始类型进行判断(valueOf() 方法)
  6. 如果有一个是 NaN,则直接返回 false
  7. 如果两个都是对象,则比较是否指向同一个对象 ==比较

3、[] == ![] 的值为什么?

答案:为 true

转换步骤

  1. ! 运算符优先级最高,![] 会被转换为 false,因此此时为 [] == false
  2. 根据第四条,其中一方为 boolean,把 boolean 转为 number,所以此时为 [] == 0
  3. 再根据第五条,把数组 [] 转为原始类型,调用数组的 toString() 方法,[].toString() = '',所以此时为 '' == 0
  4. 再根据第三条,把 string 转为 number'' 转为 number 为 0,所以此时 0 == 0
  5. 两边数据类型相同 0 == 0,为 true

4、Object.is() 判断两值相等的情况

不会进行强制类型转换

  • 都是 undefined
  • 都是 null
  • 都是 truefalse
  • 都是相同长度的字符串且相同字符按相同顺序排列
  • 都是相同对象(意味着每个对象有同一个引用)
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 或都是非零而且非 NaN 且为同一个值

十七、防抖和节流

1、什么是防抖和节流

防抖:是多次执行改为最后一次执行 节流:是将多次执行改为每隔一段时间执行

2、简单实现防抖和节流

1. 防抖实现

思路: 触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间,每次触发事件时都取消之前的延时调用方法

function debounce (fn, time = 500) {
  let timeout = null; // 创建一个标记用来存放定时器的返回值
  return function () {
    clearTimeout(timeout) // 每当触发时,把前一个 定时器 clear 掉
    timeout = setTimeout(() => { // 创建一个新的 定时器,并赋值给 timeout
      fn.apply(this, arguments)
    }, time)
  }
}
function testDebounce () {
  console.log('测试防抖')
}
const inp = document.getElementById('testInp')
inp.addEventListener('input', debounce(testDebounce))

2. 节流实现

高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率,每次触发事件时都判断当前是否有等待执行的延时函数

function throttle (fn, time = 100) {
  let timeout;
  return function () {
    let context = this
    let args = arguments
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null
        fn.apply(context, args)
      }, time)
    }
  }
}
function testThro () {
  console.log('测试节流')
}
const inp = document.getElementById('testInp')
inp.addEventListener('input', throttle(testThro))

十八、cookiesessionStoragelocalStorage

1、三者区别

  • cookie 用来保存登录信息,大小限制为 4KB 左右
  • localStorageHtml5 新增的,用于本地数据存储,保存的数据没有过期时间,一般浏览器大小限制在 5MB
  • sessionStorage 接口方法和 localStorage 类似,但保存的数据的只会在当前会话中保存下来,页面关闭后会被清空。
名称生命期大小限制与服务器通信是否可以跨域
cookie一般由服务器生成,可设置失效时间。如果在浏览器端生成 Cookie,默认是关闭浏览器后失效4KB每次都会携带在 HTTP 头中,如果使用 cookie 保存过多数据会带来性能问题一般不可,相同 domain 下可以允许接口请求携带 cookie
localStorage除非被清除,否则永久保存5MB仅在浏览器中保存,不与服务器通信不可
sessionStorage仅在当前会话下有效,关闭页面或浏览器后被清除5MB仅在浏览器中保存,不与服务器通信不可

2、localStorage 进行怎么进行跨域存储?

localStorage 是不可以进行跨域操作的,但是想进行跨域操作可以使用 postMessagewebsocket 进行变相的跨域操作。

十九、浏览器跨域问题

1、什么是浏览器同源策略?

同源策略是一个重要的安全策略,它用于限制一个 origin 的文档或者它加载的脚本如何能与另一个源的资源进行交互,它能帮助阻隔恶意文档,减少可能被攻击的媒介。

所谓同源策略,是指只有在地址的:

  1. 协议名
  2. 域名
  3. 端口名

均一样的情况下,才允许访问相同的 cookielocalStorage,以及访问页面的 DOM 或是发送 Ajax 请求。

2、没有同源策略限制有哪些危险场景?

  • ajxa 请求
  • Dom 的查询 同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。

3、为什么浏览器会禁止跨域?

  • 跨域只存在浏览器端,因为浏览器的形态很开放,需要对它进行限制。
  • 同源策略用于保护用户信息安全,防止恶意窃取数据(ajax 同源策略、Dom 同源策略)。

4、跨域有哪些解决方式?

CSDN 跨域问题解决

  1. jsonp
  2. cors
  3. postMessage
  4. websocket
  5. Node 中间件代理(两次跨域)
  6. nginx 反向代理
  7. window.name + iframe
  8. location.hash + iframe
  9. document.domain + iframe

5、CORS 常用的配置有哪些?

  • Access-Control-Allow-Origin 允许的域名
  • Access-Control-Allow-Methods 允许的 http 请求方法
  • Access-Control-Allow-Headers 支持的请求头
  • Access-Control-Allow-Credentials 是否发送 cookie
  • Access-Control-Max-Age 以秒为单位的缓存时间

6、CORS 跨域的判定流程

  1. 浏览器先判断是否同源,若同源,直接发送数据,否则,发送跨域请求;
  2. 服务器收到跨域请求后,根据自身配置返回对应的文件头;
  3. 浏览器根据收到的响应头里的 Access-Control-Allow-origin 字段进行匹配,若无该字段说明不允许跨域,报错,有该字段进行比对,判断是否可以跨域。

7、什么是简单请求?

简单请求是指满足以下条件的:

  • 使用 getposthead 其中一种方法进行请求的;
  • http 的头信息不超出一下情况:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID Content-Type:值仅限于 application/x-www-form-urlencodedmultipart/form-datatext/plain
  • 请求中 XMLHttpRequestUpload 对象没有注册任何的事件监听器;
  • XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 请求中没有使用 ReadableStream 对象。

8、非简单请求

对服务器有特殊要求的请求(简单请求之外就是非简单请求)。

例如:请求方式是 putdeleteContent-Type 的类型是 application/json

非简单请求会在正式通信前使用 options 发起一个预检请求,询问服务器当前的域名是否在服务器允许的名单之中,以及使用哪些头信息字段等。

9、有哪些方法可一嵌入跨源的资源?

  • script 标签,嵌入跨域脚本;
  • link 标签,嵌入 css
  • img 标签,嵌入图片;
  • video/audio 标签,嵌入视频、音频;
  • object/embed/applet 标签,嵌入 svg /图片 等;
  • svg 标签,嵌入 svg
  • 通过 @font-face 嵌入字体;
  • 通过 iframe 嵌入资源

10、手动实现一个 JSONP

//Promise封装
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    // 创建 script 标签
    let script = document.createElement('script')
    // 把 callback 挂载在 window 上,执行之后删除 script 
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    // 添加参数
    params = { ...params, callback } // wd=b&callback=callFun
    let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    // 设置 script 的 URL
    script.src = `${url}?${arrs.join('&')}`
    // 插入 body 中
    document.body.appendChild(script)
  })
}
// 调用示例
jsonp({
  url: 'http://localhost:3000/code',
  params: { wd: 'hello world' },
  callback: 'callFun'
}).then(data => {
  console.log(data) // 你好啊
  //再此回调结束后删除该script
})

二十、说下 js 的继承方式

JS 常见的六种继承方式

1、原型链继承 prototype

子类型的原型为父类型的一个实例对象。

Child.prototype = new Parent()

优点:

  1. 继承方式简单
  2. 父类新增方法、属性,子类都能访问到 缺点:
  3. 无法实现多继承
  4. 来自父类的所有属性被所有实例共享
  5. 要想为子类新增属性和方法,必须要在Child.prototype = new Parent() 之后,因为会被覆盖
  6. 创建子类时,不能像父类传递参数

2、构造函数继承 call

在子类型构造函数中通用 call() 调用父类型构造函数

function Child(name, age, price) {
    Parent.call(this, name, age)  // 相当于: this.Parent(name, age)
}

优点:

  1. 原型链继承中子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象) 缺点:
  4. 实例并不是父类的实例,只是子类的实例
  5. 只能继承父类的实例属性和方法,不能继承原型属性和方法
  6. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3、原型链+构造函数的组合继承 prototype + call

调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。

function Child (name, age, price) {
  Parent.call(this, name, age)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child//组合继承也是需要修复构造函数指向的

优点:

  1. 可以继承实例属性/方法,也可以继承原型属性/方法
  2. 不存在引用属性共享问题
  3. 可传参 缺点:
  4. 调用了两次父类构造函数,生成了两份实例

4、组合继承优化1

通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。

function Child (name, age, price) {
    Parent.call(this, name, age)
}
Child.prototype = Parent.prototype

优点:

  1. 不会调用了两次父类构造函数 缺点:
  2. 没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个。

5、组合继承优化2

借助原型可以基于已有的对象来创建对象,var B = Object.create(A)A 对象为原型,生成了 B 对象。B 继承了 A 的所有属性和方法。

function Child (name, age, price) {
    Parent.call(this, name, age)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

6、es6 class 的继承

class 关键字只是原型的语法糖,JavaScript 继承仍然是基于原型实现的。

class Parent {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    setName () {
        console.log('parent')
    }
}
let child1 = new Parent('name1', 18)
let child2 = new Parent('name2', 16)
class Child extends Parent {
    constructor(name, age, price) {
        super(name, age)
        this.price = price
    }
    setAge () {
        console.log('子类方法')
    }
}
let child3 = new Child('name3', 20, 15000)
let child4 = new Child('name4', 21, 10000)

优点:

  1. 简单继承

二十一、排序算法

1、冒泡排序

简单来说就是相邻两个元素进行对比,按照你需要的排序方式(升序or降序)进行位置替换,替换时需要额外一个变量当作中间变量去暂存值。 冒泡排序

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        //相邻元素两两对比
                var temp = arr[j+1];        //元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

2、快速排序

选择一个基准,将比基准小的放左边,比基准小的放在右边(基准处在中间位置) 快速排序

function quickSort(arr) {
    //如果数组<=1,则直接返回
    if (arr.length <= 1) { return arr; }
    var pivotIndex = Math.floor(arr.length / 2);
    //找基准,并把基准从原数组删除
    var pivot = arr.splice(pivotIndex, 1)[0];
    //定义左右数组
    var left = [];
    var right = [];
    //比基准小的放在left,比基准大的放在right
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] <= pivot) {
            left.push(arr[i]);
        }
        else {
            right.push(arr[i]);
        }
    }
    //递归
    return quickSort(left).concat([pivot], quickSort(right));
}

3、选择排序

首先从原始数组中找到最小的元素,并把该元素放在数组的最前面,然后再从剩下的元素中寻找最小的元素,放在之前最小元素的后面,直到排序完毕 选择排序

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
      minIndex = i;
      for (var j = i + 1; j < len; j++) {
        if (arr[j] < arr[minIndex]) {
          minIndex = j;
        }
      }
      temp = arr[i];
      arr[i] = arr[minIndex];
      arr[minIndex] = temp;
    }
    return arr;
}

4、插入排序

从第二个元素开始(假定第一个元素已经排序了),取出这个元素,在已经排序的元素中从后向前进行比较,如果该元素大于这个元素,就将该元素移动到下一个位置,然后继续向前进行比较,直到找到小于或者等于该元素的位置,将该元素插入到这个位置后.重复这个步骤直到排序完成 插入排序

function insertionSort(arr) {
  var len = arr.length;
  var preIndex, current;
  for (var i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}

5、归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。 归并排序

function mergeSort(arr) {  //采用自上而下的递归方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right){
    var result = [];
    console.time('归并排序耗时');
    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }
    while (left.length)
        result.push(left.shift());
    while (right.length)
        result.push(right.shift());
    console.timeEnd('归并排序耗时');
    return result;
}

6、希尔排序

利用步长来进行两两元素比较,然后缩减步长在进行排序。 说明:希尔排序的实质是分组插入排序,该方法又称缩小增量排序。该方法的基本思想是:先将整个待排元素序列分割为若干个子序列(由相隔某个‘增量’的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,带这个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况)效率是很高的,因此希尔排序在时间效率上有较大的提高。 与插入排序的不同之处:它会优先比较距离较远的元素 希尔排序

function shellSort(arr) {
  let temp,
    gap = 1;
  while (gap < arr.length / 3) {
    gap = gap * 3 + 1//动态定义间隔序列
  }
  for (gap; gap > 0; gap = Math.floor(gap / 3)) {//控制步长(间隔)并不断缩小
    for (var i = gap; i < arr.length; i++) {//按照增量个数对序列进行排序
      temp = arr[i]
      for (var j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {//例:j=0  arr[1]>arr[5]
        arr[j + gap] = arr[j]
      }
      arr[j + gap] = temp
    }
  }
  return arr
}

7、各类排序算法比较

算法比较

二十二、时间复杂度、空间复杂度

1、如何去衡量不同算法之间的优劣呢?

主要还是从算法所占用的「时间」和「空间」两个维度去考量。

  • 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
  • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。

2、时间复杂度

1. 表示方法

大O符号表示法 」,即 T(n) = O(f(n))

时间复杂度的公式是: T(n) = O( f(n) ),其中 f(n) 表示每行代码执行次数之和,而 O 表示正比例关系,这个公式的全称是:算法的渐进时间复杂度。

2. 常见的复杂度量级

• 常数阶 O(1) • 对数阶 O(logN) • 线性阶 O(n) • 线性对数阶 O(nlogN) • 平方阶 O(n²) • 立方阶 O(n³) • K次方阶 O(n^k) • 指数阶 (2^n)

3. O(1)

无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是 O(1)

var i = 1;
var j = 2;
++i;
j++;
var m = i + j;

4. O(n)

for 循环里面的代码会执行 n 遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用 O(n) 来表示它的时间复杂度。

for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}

5. 对数阶 O(logN)

var i = 1;
while(i<n)
{
    i = i * 2;
}

while 循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环 x 次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2x 次方等于 n,那么 x = log2^n 也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)

6. O(nlogN)

将时间复杂度为 O(logn) 的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了 O(nlogN)

for(m=1; m<n; m++)
{
    i = 1;
    while(i<n)
    {
        i = i * 2;
    }
}

7. O(n²)O(m*n)O(n³)O(n^k)

平方阶 O(n²)O(n) 的代码再嵌套循环一遍,它的时间复杂度就是O(n*n),即 O(n²)

for(x=1; i<=n; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}

O(m*n) 将其中一层循环的 n 改成 m,那它的时间复杂度就变成了 O(m*n)

for(x=1; i<=m; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}

3、空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。

1. 常见的复杂度量级

空间复杂度比较常用的有:O(1)、O(n)、O(n²)

2. O(1)

如果算法执行所需要的临时空间不随着某个变量 n 的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)

var i = 1;
var j = 2;
++i;
j++;
var m = i + j;

代码中的 ijm 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)

3. O(n)

var arr = [1, 2, 3]
for(i=1; i<=arr.lemgth; ++i)
{
   j = i;
   j++;
}

第一行定义了一个数组出来,这个数据占用的大小为 n,这段代码的 2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)

二十三、接口请求

1、AJAX

1、简单实现一个 ajax

function stringify (json) {
  var str = "";
  for (var i in json) {
    str += i + "=" + json[i] + "&";
  }
  return str.slice(0, -1);
}
function myAjax (type, url, params, callback, errback) {
  let xhr = null;
  //表IE
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else {
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }
  if (type == "get") {
    xhr.open(type, url + "?" + stringify(params), true);
    xhr.send();
  } else {
    xhr.open(type, url, true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    //json转换成name=张三&age=1
    xhr.send(stringify(params));
  }
  xhr.onreadystatechange = function () {
    // 表示请求已完成
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        if (callback) {
          callback(xhr.responseText);
        }
      } else {
        errback && errback();
      }
    }
  }
}

2、ajaxreadyState 的状态

  • 0 未初始化,还没有调用 open() 方法
  • 1 启动,已经调用 open() 方法,但是没有调用 send() 方法
  • 2 发送,已经调用 send() 方法,但是尚未接收响应
  • 3 接收,已经接收到部分响应数据
  • 4 完成,已经接收到全部响应数据

2、Axios

Axios 本质上也是对原生 XHR 的封装,只不过它是 Promise 的实现版本,符合最新的 ES 规范

1. 特性:

  • node.js 创建 http 请求
  • 支持 Promise API
  • 客户端支持防止 CSRF
  • 提供了一些并发请求的接口(重要,方便了很多的操作)

3、Fetch

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

1. 优点

  • 语法简洁,更加语义化
  • 基于标准 Promise 实现,支持 async/await
  • 同构方便,使用 isomorphic-fetch

2. 缺点

  • Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject

二十四、new 操作符

1、new 的实现流程

  • 1.创建一个新对象;
  • 2.将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  • 3.执行构造函数中的代码(为这个新对象添加属性);
  • 4.返回新对象。
  • 5.将构造函数的 prototype 关联到实例的 __proto__

2、如何实现一个 new

function myNew (foo, ...args) {
  // 创建一个新对象,并继承 foo 的 prototype 属性
    let obj = Object.create(foo.prototype)
  // 执行构造方法,并绑定新 this,
  let result = foo.apply(obj, args)
  // 如果构造方法返回了一个对象,那么就返回该对象,否则就返回 myNew 创建的新对象
  return Object.prototype.toString().call(result) === '[object Object]' ? result : obj
}

二十五、网页全屏怎么实现?

document.documentElement.requestFullscreen()

需要兼容实现

1、网页全屏

function fullScreen() {
    if (!document.fullscreenElement &&
        !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { // current working methods
        if (document.documentElement.requestFullscreen) {
            document.documentElement.requestFullscreen();
        } else if (document.documentElement.msRequestFullscreen) {
            document.documentElement.msRequestFullscreen();
        } else if (document.documentElement.mozRequestFullScreen) {
            document.documentElement.mozRequestFullScreen();
        } else if (document.documentElement.webkitRequestFullscreen) {
            document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
        }
    }
}

2、取消网页全屏

function exitFullScreen() {
    if (document.exitFullscreen) {
        document.exitFullscreen();
    } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
    } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
    }
}

3、检测是否全屏

/**
 * 检查是否全屏
 * @return {[Boolean]} [是否全屏,为 true 没有全屏,false 全屏]
 */
function checkFullScreenValue () {
    return !document.fullscreenElement &&
        !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement
}

二十六、MapWeakMapsetWeakSet 有什么区别?

WeakMapWeakSet 都是弱引用

1、什么是弱引用

弱引用是指不能确保其引用的对象不会被垃圾回收器回收的引用,换句话说就是可能在任意时间被回收。

弱引用随时都会消失,遍历机制无法保证成员的存在

2、Set

  • 遍历顺序:插入顺序
  • 没有键只有值,可认为键和值两值相等
  • 添加多个 NaN 时,只会存在一个 NaN
  • 添加相同的对象时,会认为是不同的对象
  • 添加值时不会发生类型转换(5 !== "5")
  • keys()values() 的行为完全一致,entries() 返回的遍历器同时包括键和值且两值相等

3、weakSet 作用

  • Set 结构类似,成员值只能是对象
  • 储存 DOM 节点:DOM 节点被移除时自动释放此成员,不用担心这些节点从文档移除时会引发内存泄漏
  • 临时存放一组对象或存放跟对象绑定的信息:只要这些对象在外部消失,它在 WeakSet 结构中的引用就会自动消
  • 成员都是弱引用,垃圾回收机制不考虑 WeakSet 结构对此成员的引用
  • 成员不适合引用,它会随时消失,因此 ES6 规定 WeakSet 结构不可遍历
  • 其他对象不再引用成员时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于 WeakSet 结构中

4、Map

  • 遍历顺序:插入顺序
  • 对同一个键多次赋值,后面的值将覆盖前面的值
  • 对同一个对象的引用,被视为一个键
  • 对同样值的两个实例,被视为两个键
  • 键跟内存地址绑定,只要内存地址不一样就视为两个键
  • 添加多个以 NaN 作为键时,只会存在一个以 NaN 作为键的值
  • Object 结构提供字符串—值的对应,Map 结构提供值—值的对应

5、WeakMap

  • Map 结构类似,成员键只能是对象
  • 储存 DOM 节点:DOM 节点被移除时自动释放此成员键,不用担心这些节点从文档移除时会引发内存泄漏
  • 部署私有属性:内部属性是实例的弱引用,删除实例时它们也随之消失,不会造成内存泄漏
  • 成员键都是弱引用,垃圾回收机制不考虑 WeakMap 结构对此成员键的引用
  • 成员键不适合引用,它会随时消失,因此 ES6 规定 WeakMap 结构不可遍历
  • 其他对象不再引用成员键时,垃圾回收机制会自动回收此成员所占用的内存,不考虑此成员是否还存在于 WeakMap 结构中
  • 一旦不再需要,成员会自动消失,不用手动删除引用
  • 弱引用的只是键而不是值,值依然是正常引用
  • 即使在外部消除了成员键的引用,内部的成员值依然存在

6、Object 转为 Map

let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj))

二十七、Proxy

1、语法

  • target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
  • handler 一个通常以函数作为属性的对象,用来定制拦截行为 代理只会对 proxy 对象生效,对代理对象没有任何效果
origin = {}
obj = new Proxy(origin, {
  get: function (target, propKey, receiver) {
        return '10'
  }
});
obj.a // 10
obj.b // 10
origin.a // undefined
origin.b // undefined

2、Handler 对象常用的方法

方法描述
handler.has()in 操作符的捕捉器。
handler.get()属性读取操作的捕捉器。
handler.set()属性设置操作的捕捉器。
handler.deleteProperty()delete 操作符的捕捉器。
handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply()函数调用操作的捕捉器。
handler.construct()new 操作符的捕捉器

3、proxy 代理是否可以撤销?

proxy 有一个唯一的静态方法,Proxy.revocable(target, handler) Proxy.revocable() 方法可以用来创建一个可撤销的代理对象 该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}

  • proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。
const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked //已撤销

二十八、执行上下文

1、执行上下文的类型?

1. 全局执行上下文:

一个程序中只能存在一个全局执行上下文。

这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:

  1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。
  2. this 指针指向这个全局对象。

2. 函数执行上下文:

可以有无数个函数执行上下文。

每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。

3. Eval 函数执行上下文:

jseval 函数执行其内部的代码会创建属于自己的执行上下文, 很少用而且不建议使用。

2、执行上下文的特点

  1. 单线程;
  2. 同步执行,从上往下顺序执行;
  3. 全局上下文只有一个,也就是 window 对象;
  4. 函数执行上下文没有数量限制;
  5. 函数只有在调用的时候才会被创建,每调用一次就会产生一个新的执行上下文环境。

3、执行上下文的生命周期

1. 创建阶段

  1. 创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明。
  2. 创建作用域链:作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
  3. 确定 this 指向:确定 this 的指向。

2. 执行阶段

  1. 执行变量赋值。
  2. 函数引用。
  3. 执行其他代码。

3. 回收阶段

  1. 执行上下文出栈
  2. 等待虚拟机回收执行上下文

4、js 如何管理多个执行上下文的?

管理多个执行上下文靠的就是执行栈,也被叫做调用栈。

特点:后进先出(LIFO:last-in, first-out)的结构。 作用:存储在代码执行期间的所有执行上下文。

示例:

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

执行上下文

二十九、实现一些特殊函数

1、一次性函数

function once (func) {
  let done;
  return function () {
    if (!done) {
      func.apply(null, arguments)
      done = true
    }
  }
}
const onlyDoOne = once(function() {
  console.log('1')
})
onlyDoOne() // 1
onlyDoOne() // 没有输出,不会再次执行

2、延迟函数(沉睡函数)

function sleep (time) {
    return new Promise(resolve => {
    window.setTimeout(resolve, time)
  })
}
// 调用
sleep(1000).then(res => {
    console.log('延迟')
})
// 调用
async function useSleep () {
    const sleepval = await sleep(1000)
}
useSleep()

3、setTimeout 实现 setInterval

;(() => {
  const list = new Set();
  function myInterval(fn, ms) {
    const ref = {};
    const exec = () => {
      return setTimeout(() => {
        fn.apply(null);
        const timer = exec();
        ref.current = timer;
      }, ms);
    };
    ref.current = exec();
    list.add(ref);
    return ref;
  }
  function myClearInterval(ref) {
    clearTimeout(ref.current);
    list.delete(ref);
  }
  window.myInterval = myInterval;
  window.myClearInterval = myClearInterval;
})()
myInterval(() => {console.log(1)}, 5000)
myClearInterval({current: 1186})

4、前端生成 excel 表格并下载

/**
 * 前端下载表格
 * @param  {[Array]} data      [数据数组]
 * @param  {[String]} tableHead [表头字符串]
 * @return {[undefined]}           
 */
function downExcel (data, tableHead) {
  tableHead = tableHead
  data.forEach(item => {
    for (let i in item) {
      tableHead += `${item[i] + '\t'},`
    }
    tableHead += '\n'
  })
  const url = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(tableHead);
  //通过创建a标签实现
  const link = document.createElement("a");
  link.href = url;
  //对下载的文件命名
  link.download = "我的EXCEL表格.csv";
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}
// excel 数据
let tableData = [{
  name: '你好啊',
  time: 130000000000,
  pre: '127.130',
  source: '淘宝',
  otherTime: 1571276232000
}]
// excel 头部
let str = `用户名,时间,坐标,来源,授权时间\n`;
// 下载表格执行
downExcel(tableData, str)

掘金不能发布六万字文章,所以拆成了三部分

六万字、42个知识点、超长篇幅助你了解 JS(一)JS 面试大全

六万字、42个知识点、超长篇幅助你了解 JS(二)JS 面试大全

六万字、42个知识点、超长篇幅助你了解 JS(三)JS 面试大全