• 【前端知识体系-JS相关】ES6专题系列总结


    1.如何搭建ES6的webpack开发环境?

    1. 安装Node环境
      node -v // 10.14.1

    2. 安装NPM环境
      npm -v // 6.4.1

    3. 安装babel
      npm install @babel/core babel-preset-es2015 babel-preset-latest

    4. 创建.babelrc文件
      npm install babel-cli -g
      babel --version
      babel ./src/index.js // 测试babel的编译结构

    5. Webpack---模块化工具安装
      npm install webpack babel-loader --save-dev // 安装webpack,babel-loader

    6. 配置webpack.config.js

    7. 配置package.json中的scripts

    8. 运行n npm start

    1.1 常见错误

        0x01产生原因
        babel-loader和babel-core版本不对应所产生的,
        babel-loader 8.x对应babel-core 7.x
        babel-loader 7.x对应babel-core 6.x
    

    1.2 安装环境注意事项

      1. 这里的安装的版本必须是@babel/core,否则会出现版本不兼容的错误
      1. 建议先安装webpack,然后安装loader

    2.谈一下JS的模块化?

    1. 最开始的时候JS是没有模块化的
    2. AMD(Async module define)成为标准,require.js(CMD--common-module-define)
    3. 前端打包工具,使得nodejs模块化可以被使用 (Grunt, Gulp, Webpack……)
    4. ES6的出现,想统一现在所有的模块化标准
    5. nodejs(服务器端)积极支持,浏览器(需要打包为ES5/4)尚未统一
    6. 可以自造库lib,但是不要自造标准

    2.1 rollup的使用

      1. 功能单一,可集成,可扩展
      1. rollup + gulp的结合
    • 安装
      npm install rollup rollup-plugin-node-resolve roll-up-plugin-babel babel-plugin-external-helpers babel-preset-latest --save-dev
    • 配置 .babelrc
    • 配置 rollup.config.js
    • 修改 package.json scripts
    • 运行命令:npm start

    2.2 rollup特点

    • rollup功能单一,webpack功能强大
    • 参考设计原则和《Linux/Unix设计思想》
    • 工具要尽量功能单一,可以继集成,可扩展
    • 可以使用gulp + rollup(主流框架VUE和React使用的就是rollup进行模块化的)

    3.class和JS普通构造函数的区别?

    console.log(typeof MathHandle, typeof  MathHandle.prototype.constructor);   // function function
    // 1. 构造函数的原型里面默认会有一个constructor属性,这个属性的值等于这个构造函数本身
    console.log(MathHandle === MathHandle.prototype.constructor)        // true
    console.log(MathHandle.prototype);
    // 2. 所有实例的隐式原型__proto__和这个构造函数的显示原型prototype是相同的
    console.log(m.__proto__ === MathHandle.prototype);      // true
    
    console.log('-------------------------------------------------------------')
    console.log(typeof MathHandler, typeof  MathHandler.prototype.constructor)         // 'function'
    console.log(MathHandler === MathHandler.prototype.constructor)  // true
    console.log(MathHandler.prototype);
    console.log(mm.__proto__=== MathHandler.prototype);
    

    [!NOTE]

    1. class 在语法上更加贴合面向对象的写法
    1. class 实现继承更加易读,易理解
    2. 更加易于些java等后端语言的使用
    3. class【本质】还是语法糖,实际上还是使用的是prototype

    4. 写实现一个Promise?

    先看看Promise是怎么用的

    new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(1)
      }, 0)
    }).then(value => {
      console.log(value)
    })
    

    在Promise的构造器中传入一个函数,这个函数有两个参数 resolve和reject,这两个参数都是Promise的回调函数,不需要自己写,在需要的时候调用就可以了,他们分别是成功的回调resolve和失败的回调reject。

    4.1 简易版 Promise

    第一步,先来搭建构建函数的大体框架

    const PENDING = 'pending'
    const RESOLVED = 'resolved'
    const REJECTED = 'rejected'
    
    function MyPromise(fn){
      const that = this
      that.state = PENDING
      that.value = null
      that.resolvedCallbacks = []
      that.rejectedCallbacks = []
      // 待完善 resolve 和 reject 函数
      // 待完善执行 fn 函数
    }
    
    • 首先创建三个常量用于表示状态,对于经常使用的一些值应该通过常量来管理,便于开发及后期维护
    • 在函数体内部首先创建常量that,因为代码可能会异步执行,用于获取正确的this对象
    • 一开始Promise的状态是 pending
    • value 变量用于保存resolve或者reject中传入的值
    • resolvedCallbacks和rejectedCallback用于保存then中的回调,因为当执行完Promise时状态可能还是等待中,这时候应该把then中的回调保存起来用于状态改变时使用

    第二步,完善resolve和reject函数,添加在 MyPromise 函数体内部

    function resolve(value) {
      if(that.state === PENDING) {
        that.state = RESOLVED
        that.value = value
        that.resolvedCallbacks.map(cb => cb(that.value))
      }
    }
    
    function reject(value) {
      if(that.state === PENDING){
        that.state = REJECTED
        that.value = value;
        that.rejectedCallbacks.map(cb => cb(that.value));
      }
    }
    
    • 首先两个函数都得判断当前状态是否为等待中,因为规范规定只有等待态才可以改变状态
    • 将当前状态更改为对应状态,并且将传入的值赋值给 value
    • 遍历回调数组并执行

    第四步,实现如何执行 Promise 中传入的函数了

    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
    
    • 实现很简单,执行传入的参数并且将之前两个函数当做参数传进去
    • 要注意的是,可能执行函数过程中会遇到错误,需要捕获错误并且执行 reject 函数

    第五步,实现较为复杂的 then 函数。

    then函数是在Promise构造器中成功状态下调用的resolve方法的回调。

    then函数是可以接收两个参数的,一个是用户自定义的成功处理,另一个是用户自定义的错误处理,第二个参数可不传。

    MyPromise.prototype.then = function(onFulfilled, onRejected) {
      const that = this
      //对传入的两个参数做判断,如果不是函数将其转为函数
      onFulfilled =
        typeof onFulfilled === 'function'
        ? onFulfilled
        : v => v  // onFulfilled = v => v
      onRejected =
        typeof onRejected === 'function'
        ? onRejected
        : r => {
          throw r
        }
    
      if(that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallbacks.push(onRejected)
      }
      else if(that.state === RESOLVED) {
        onFulfilled(that.value)
      }
      else {
        onRejected(that.value)
      }
    }
    
    • 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
    • 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传,比如如下代码
    // 该代码目前在简单版中会报错
    // 只是作为一个透传的例子
    Promise.resolve(4).then().then((value) => console.log(value))
    
    • 接下来就是一系列判断状态的逻辑,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数,比如如下代码就会进入等待态的逻辑
    • 可参考:https://www.cnblogs.com/minigrasshopper/p/9141307.html

    5.ES6中常用的功能有哪些?

    1. let/const
    2. 多行字符串/模板变量
    3. 解构赋值
    4. 块级作用域?【重点理解】
    5. 函数默认参数
    6. 箭头函数(this指向问题)

    5.1 var、let 及 const 区别?

    • var声明的变量会挂载在window上,而let和const不会
    • var声明变量存在变量提升,let和const不会
    • let、const 的作用范围是块级作用域,而var的作用范围是函数作用域
    • 同一作用域下let和const不能声明同名变量,而var可以
    • 同一作用域下在let和const声明前使用会存在暂时性死区
    • const
      • 一旦声明必须赋值,不能使用null占位
      • 声明后不能再修改
      • 如果声明的是复合类型数据,可以修改其属性

    5.2 Proxy

    [!NOTE]
    Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

    let p = new Proxy(target, handler)
    

    target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

    let onWatch = (obj, setBind, getLogger) => {
      let handler = {
        set(target, property, value, receiver) {
          setBind(value, property)
          return Reflect.set(target, property, value)
        },
        get(target, property, receiver) {
          getLogger(target, property)
          return Reflect.get(target, property, receiver)
        }
      }
      return new Proxy(obj, handler)
    }
    
    let obj = { a: 1 }
    let p = onWatch(
      obj,
      (v, property) => {
        console.log(`监听到属性${property}改变为${v}`)
      },
      (target, property) => {
        console.log(`'${property}' = ${target[property]}`)
      }
    )
    p.a = 2 // 控制台输出:监听到属性a改变
    p.a // 'a' = 2
    

    [!NOTE]
    自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

    当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。

    5.3 数组方法

    5.3.1 map

    [!NOTE]
    map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后返回一个新数组,原数组不发生改变。

    map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组

    var arr = [1,2,3];
    var arr2 = arr.map(item => item + 1)    
    arr   //[ 1, 2, 3 ]
    arr2  // [ 2, 3, 4 ]
    
    ['1','2','3'].map(parseInt)
    // -> [ 1, NaN, NaN ]
    
    • 第一个 parseInt('1', 0) -> 1
    • 第二个 parseInt('2', 1) -> NaN
    • 第三个 parseInt('3', 2) -> NaN

    5.3.2 filter

    [!NOTE]
    filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素

    filter 的回调函数接受三个参数,分别是当前索引元素,索引,原数组

    5.3.3 reduce

    [!NOTE]
    reduce 可以将数组中的元素通过回调函数最终转换为一个值。

    如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码

    const arr = [1, 2, 3]
    let total = 0
    for (let i = 0; i < arr.length; i++) {
      total += arr[i]
    }
    console.log(total) //6
    

    但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码

    const arr = [1, 2, 3]
    const sum = arr.reduce((acc, current) => acc + current, 0)
    console.log(sum)
    

    对于 reduce 来说,它接受两个参数,分别是回调函数和初始值,接下来我们来分解上述代码中 reduce 的过程

    • 首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
    • 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
    • 在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入
    • 所以在第二次执行回调函数时,相加的值就分别是 1 和 2,以此类推,循环结束后得到结果 6。

    5.4 Es6中箭头函数与普通函数的区别?

    • 普通function的声明在变量提升中是最高的,箭头函数没有函数提升
    • 箭头函数没有属于自己的this,arguments
    • 箭头函数不能作为构造函数,不能被new,没有property
    • call和apply方法只有参数,没有作用域

    5.5 Promise

    Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了。

    • 等待中(pending)
    • 完成了(resolved)
    • 拒绝了(rejected)

    当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的。

    new Promise((resolve, reject) => {
      console.log('new Promise')
      resolve('success')
    })
    console.log('finifsh')
    
    // 先打印new Promise, 再打印 finifsh
    

    Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装。

    Promise.resolve(1)
      .then(res => {
        console.log(res) // => 1
        return 2 // 包装成 Promise.resolve(2)
      })
      .then(res => {
        console.log(res) // => 2
      })
    

    当然了,Promise 也很好地解决了回调地狱的问题

    ajax(url)
      .then(res => {
          console.log(res)
          return ajax(url1)
      }).then(res => {
          console.log(res)
          return ajax(url2)
      }).then(res => console.log(res))
    

    其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。

    5.6 async 和 await

    一个函数如果加上 async ,那么该函数就会返回一个 Promise

    async function test() {
      return "1"
    }
    console.log(test())
    // -> Promise {<resolved>: "1"}
    

    async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用。

    async function test() {
      let value = await sleep()
    }
    

    async 和 await 可以说是异步终极解决方案了,相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码,毕竟写一大堆 then 也很恶心,并且也能优雅地解决回调地狱问题。

    当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。

    async function test() {
      // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
      // 如果有依赖性的话,其实就是解决回调地狱的例子了
      await fetch(url)
      await fetch(url1)
      await fetch(url2)
    }
    

    看一个使用 await 的例子:

    let a = 0
    let b = async () => {
      a = a + await 10
      console.log('2', a)
    }
    b()
    a++
    console.log('1', a)
    
    //先输出  ‘1’, 1
    //在输出  ‘2’, 10
    
    • 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
    • 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
    • 同步代码 a++ 与打印 a 执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10

    [!NOTE]
    上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator

    5.7 Generator 生成器

    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next(12)) // => {value: 8, done: false}
    console.log(it.next(13)) // => {value: 42, done: true}
    
    • 首先 Generator 函数调用和普通函数不同,它会返回一个迭代器

    • 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6

    • 当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8

    • 当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42

    5.8 生成器原理

    [!NOTE]
    当yeild产生一个值后,生成器的执行上下文就会从栈中弹出。但由于迭代器一直保持着队执行上下文的引用,上下文不会丢失,不会像普通函数一样执行完后上下文就被销毁

    5.9 ES Module

    [!NOTE]
    ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别

    • CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
    • CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
    • CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
    • ES Module 会编译成 require/exports 来执行的
    // 引入模块 API
    import XXX from './a.js'
    import { XXX } from './a.js'
    // 导出模块 API
    export function a() {}
    export default function() {}
    

    6. 箭头函数和普通函数的8点区别?

    6.1 不能作为构造函数

    箭头函数作为匿名函数,是不能作为构造函数的,不能使用new

    6.2 无arguments

    箭头函数不绑定arguments,取而代之用rest参数…解决

    function A(a){
        console.log(arguments); // 1
    }
    
    var BBB = (aaa)=>{
        console.log(arguments); // 2
    }
    
    var CCC = (a, ...rest)=>{
        console.log(a, '-----', rest);         // 1, ---- [2 3 4]
    }
    
    A(1);
    BBB(2);
    CCC(1, 2, 3, 4);
    
    

    6.3 没有自己的this

    箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值

    var obj = {
        a: 10,
        b: function(){
            console.log(this.a); //10
        },
        c: function() {
            // 在c方法里面return的这个箭头函数捕获的是c:function(){}这个环境的this,而这个环境的this是obj【找出块作用域的外部一层就是他的环境】
            return ()=>{
                console.log(this.a); //10
            }
        }
    }
    obj.b();
    obj.c()();
    
    

    6.4 this环境

    箭头函数当方法使用的时候没有定义this绑定

    var obj = {
        a: 10,
        b: () => {
            // 因为箭头函数捕获的是obj{}这个对象的环境,然后这个环境的this指向的是window【找出块作用域的外部一层就是他的环境】
            console.log(this.a); //undefined
            console.log(this); //window
        },
        c: function() {
            console.log(this.a); //10
            console.log(this); //obj{...}
        }
    }
    obj.b();
    obj.c();
    
    

    6.5 this不可以被修改

    使用call()和apply()调用, 通过 call() 或 apply() 方法调用一个函数时,只是传入了参数而已,对 this并没有什么影响

    var obj = {
        a: 10,
        b: function(n){
            var f = (v) => v + this.a;
            /*function f(v) {
                return v+this.a;
            }*/
            return f(n);
        },
        c: function(n) {
            var f = (v) => v + this.a;
            var m = {a:20};
            return f.call(m,n);
        }
    }
    
    console.log(obj.b(1)); //11
    console.log(obj.c(1)); //11
    
    

    6.6 箭头函数没有原型属性

    var a = ()=>{
        return 1;
    }
    
    function b(){
        return 2;
    }
    
    console.log(a.prototype);//undefined
    console.log(b.prototype);//object{...}
    
    

    6.7 不能当做Generator函数

    箭头函数不能当做Generator函数,不能使用yield关键字

    var a = ()
        // =>1; //SyntaxError: Unexpected token =>
    

    6.8 箭头函数的this指向

    箭头函数的this永远指向其上下文的 this,任何方法都改变不了其指向,如call(), bind(), apply(),普通函数的this指向调用它的那个对象

    var obj = {
        say: function () {
            // 箭头函数
            var f1 = () => {
                console.log(this); // obj
                setTimeout(() => {
                    console.log(this); // obj
                })
            };
            f1();
        }
    }
    
    var obj = {
        say: function () {
            // 普通函数
            var f1 = function () {
                console.log(this);    // window, f1调用时,没有宿主对象,默认是window
                setTimeout(() => {
                    console.log(this); // window
                })
            };
            f1();
        }
    
  • 相关阅读:
    从首页看CCS布局
    Community Server专题一:概述Community Server
    datagrid程序增加列的方法
    类的关键字
    base 关键字用于从派生类中访问基类的成员:
    关于CS1.1后台管理页面的研究
    如何:创建同步 HTTP 处理程序
    Community Server专题二:体系结构
    SqlTransaction 类
    单继承与多实现
  • 原文地址:https://www.cnblogs.com/fecommunity/p/11917838.html
Copyright © 2020-2023  润新知