小程序使用canvas进行图片缩放和压缩

封装了一个插件

使用方式:
html和css部分

 //html页面底部放一个canvas元素
// 隐藏掉canvas
.myCanvas{
    border: solid 1rpx #ccc;
    position: fixed;
    left: -10000rpx;
}

// 下面是js逻辑

let {ImageCompressor} = require("../../../utils/imageCompressor.js") // 引入插件
  onLoad: function (options) {
    // 页面加载后初始化插件,传入必须的参数
    this.imageCompressor =  new ImageCompressor({
      VueComponent:this,
      canvasId:'myCanvas',
    })
  }
  ...
  uploadImg: function () {
    let that = this
    wx.chooseImage({
      count: 9, // 默认9
      sizeType: ["original", "compressed"], // 可以指定是原图还是压缩图,默认二者都有
      sourceType: ["album", "camera"], // 可以指定来源是相册还是相机,默认二者都有
      success: function (res) {
        如果是多张图片,就调用插件的forEachCompressImg方法把res传进去,回调函数中可以获取到压缩后的图片地址
        that.imageCompressor.forEachCompressImg(res,function(tempFilePath){
          // 拿到压缩后的图片地址后再进行自己的上传逻辑
          that.upload_file(
            __config.basePath + "api/upload.jsp?uid=" + that.data.uid,
            tempFilePath
          )
        })
      },
    })
  },

插件代码,建议放入到util目录下

/**
 * 使用Canvas进行图片缩放和压缩
 * @param {Object} VueComponent vue组件的实例
 * @param {String} canvasId canvas-id
 * @param {Number} imgMaxSize 最大的图片宽高尺寸
 */
export class ImageCompressor {
  constructor(option) {
    this.VueComponent = option.VueComponent || {}
    this.canvasId = option.canvasId || ""
    this.imgMaxSize = option.imgMaxSize || 500
    this.init()
  }
  init() {
    console.log("init done", this)
  }
  /**
   * 批量压缩图片函数
   * @param {Object} res wx.chooseImage返回的res数据对象,通过回调返回压缩后的图片地址
   * @param {Function} callBack
   */
  async forEachCompressImg(res = {}, callBack = {}) {
    let that = this
    for (let [index, item] of res.tempFilePaths.entries()) {
      console.log("-----------------------------------")
      console.log("原始图片size:", res.tempFiles[index].size / 1024 + "k")
      let imgPath = res.tempFilePaths[index]
      let resImg = await that.imgCompress(imgPath)
      wx.getFileInfo({
        filePath: resImg.tempFilePath,
        success(res) {
          console.log("compressImage压缩后的图片size", res.size / 1024 + "k")
        },
      })
      callBack && callBack(resImg.tempFilePath)
    }
  }
  /**
   * 单个图片压缩
   * @param {String} imgSrc 临时图片路径
   */
  imgCompress(imgSrc = "") {
    let that = this
    let VueComponent = this.VueComponent
    return new Promise((resolve, reject) => {
      wx.getImageInfo({
        src: imgSrc,
        success: function (res) {
          //---------利用canvas压缩图片--------------
          var ratio = 2
          var canvasWidth = res.width //图片原始长宽
          var canvasHeight = res.height
          var limit = that.imgMaxSize // 宽高限制
          while (canvasWidth > limit || canvasHeight > limit) {
            // 保证宽高在200以内
            canvasWidth = Math.trunc(res.width / ratio)
            canvasHeight = Math.trunc(res.height / ratio)
            ratio++
          }
          VueComponent.setData({
            cWidth: canvasWidth,
            cHeight: canvasHeight,
          })
          var ctx = wx.createCanvasContext(that.canvasId, VueComponent)
          ctx.drawImage(res.path, 0, 0, canvasWidth, canvasHeight)
          ctx.draw(
            false,
            setTimeout(function () {
              console.log(2, "压缩前图片地址", res.path)
              wx.canvasToTempFilePath({
                canvasId: that.canvasId,
                destWidth: canvasWidth,
                destHeight: canvasHeight,
                quality: 0.5, // 压缩倍率
                fileType: "jpg",
                success: function (res) {
                  resolve(res)
                },
                fail: function (res) {
                  reject(res.errMsg)
                },
              })
            }, 300) //延迟500ms,避免部分机型未绘制出图片内容就执行保存操作,导致最终的图片是块白色画板。
          )
        },
        fail: function (res) {
          reject(res.errMsg)
        },
      })
    })
  }
}

windows下nodejs多版本切换,使用nvm方式

  1. 后验证是否成功 nvm v
  2. 进入nvm安装路径C:\Users\{你的用户名}\AppData\Roaming\nvm下打开settings文件
  3. 加入下列配置下载源镜像源
    1. node_mirror: https://npm.taobao.org/mirrors/node/
    2. npm_mirror: https://npm.taobao.org/mirrors/npm/
  4. 查看可用nodejs版本 nvm list available
  5. 也可以在这里查看https://registry.npmmirror.com/binary.html?path=node/
  6. 安装指定的版本 nvm install 12.17.0
  7. 查看已安装版本 nvm list
  8. 切换版本(需要管理员权限PowerShell)nvm use 12.17.0
  9. 删除指定的版本 nvm uninstall 16.14.0

github访问不了或者非常慢

之前使用的修改host的方式,体验下来还是不行。很多梯子也解决不了。

现在有很多平台提供免费github加速服务,比如UC浏览器和有道云笔记都有教育资源加速服务,也很不错,尤其是有道云笔记的教育资源加速,我正在用。
然后今天发现了个很nb的方式。就是使用steam++,不但能加速github还能加速steam的商店,但是也不稳定。

vscode使用vim模式卡顿

症状:vscode使用vim插件后很卡,尤其是中英文切换的时候

根因:Bracket Pairs Colorizer 2 等等 绘图效果插件 会重制图,导致卡

解决方法:禁用 Bracket Pairs Colorizer 2 , rainbow indent等插件

es6-es11系列8 Webpack和Babel的配置

简单来说webpack的作用就是把我们发环境的代码打包成生成环境的代码

核心概念

entry 入口的职责是要把什么进行编译
output 出口的作用是编译后成什么样子
Loader 进行文件的转换,作用是文件编译
plugin 文件转换后增强一系列的后续操作,可以理解为管道,不参与文件的编译
mode 开发环境还是生成环境

webpack配置

先放出来最终代码地址,如果你赖的自己敲可以复制对应的代码:https://github.com/yjxf8285/webpack-learn

1 首先创建一个项目文件夹,然后在终端中使用npm init -y 命令生成package.json文件,例如:

{
  "name": "webpack-learn",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

熟悉webpack核心概念
1.1 安装必要的3个依赖

"devDependencies": {
    "webpack": "4.43.0",
    "webpack-cli": "3.3.11",
    "webpack-dev-server": "3.11.0"
  }

1.2 创建webpack配置文件webpack.config.js

module.exports = {
    mode: 'development',
    // mode: 'production', // 编译后的文件会压缩
    entry: './src/index.js',
    output: {
        filename: 'index.js' // 指定输入文件的文件名
    },
}

1.3 执行编译
创建src目录,并在src目录下新建index.js文件,然后执行npx webpack命令,会生成dist目录和index.js编译后的文件

1.4 使用模板插件和拷贝文件
先安装下面这两个插件的依赖,注意版本号

"devDependencies": {
    "copy-webpack-plugin": "5.1.1",
    "html-webpack-plugin": "4.3.0",
    ...

配置文件中添加对应的配置

const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require("copy-webpack-plugin")

module.exports = {
    mode: 'development',
    // mode: 'production', // 编译后的文件会压缩
    entry: './src/index.js',
    output: {
        filename: 'index.js' // 指定输入文件的文件名
    },
    plugins: [
        // new HtmlWebpackPlugin() // 编译后自动生成html文件并引入出口的js文件
        new HtmlWebpackPlugin({
            template: './src/index.html' // 手动指定index.html的模板
        }),
        new CopyPlugin([{ from: "static", to: "static" }]), // 拷贝文件到编译后的目录
    ]
}

src目录下创建index.html模板文件,根目录创建static文件夹,然后在里面新建一些js文件。再执行编译后,可以在dist目录下得到对应的模板和拷贝过去的静态文件

2 拆分配置文件,分别为开发环境和生产环境
需要安装2个依赖,后面要用

  "devDependencies": {
    "webpack-merge": "4.2.2",
    "clean-webpack-plugin": "3.0.0",
    ...

新建一个build目录,然后创建4个文件
webpack.base.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require("copy-webpack-plugin")

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'index.js' // 指定输入文件的文件名
    },
    plugins: [
        // new HtmlWebpackPlugin() // 编译后自动生成html文件并引入出口的js文件
        new HtmlWebpackPlugin({
            template: './src/index.html' // 手动指定index.html的模板
        }),
        new CopyPlugin([{ from: "static", to: "static" }]), // 拷贝文件到编译后的目录
    ]
}

webpack.dev.config.js

module.exports = {
    devtool: 'cheap-module-eval-source-map' // 生成一个没有列信息(column-mappings)的 SourceMaps 文件,同时 loader 的 sourcemap 也被简化为只包含对应行的。
}

webpack.pro.config.js

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    plugins: [
        new CleanWebpackPlugin() // 每次编译会自动删除dist目录
    ]
}

webpack.config.js

const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const devConfig = require('./webpack.dev.config')
const proConfig = require('./webpack.pro.config')

module.exports = (env, argv) => {
    const config = argv.mode === 'development' ? devConfig : proConfig
    return merge(baseConfig, config)
}

在package.json中编辑运行脚本

 "scripts": {
    "start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
    "build": "webpack --mode=production --config ./build/webpack.config.js"
  },

现在可以根据脚本中指定的mode加载不同的配置文件,并merge基本的配置文件了

3 添加babel支持
先安装3个依赖

"@babel/core": "7.9.6",
"@babel/preset-env": "7.9.6",
"babel-loader": "8.1.0",

在webpack.base.config.js中添加规则

   module: {
        rules: [{
            test: /\.js$/, // 匹配js结尾的文件
            exclude: /node_modules/, // 排除依赖包目录
            use: {
                loader: 'babel-loader', // 使用哪个loader来翻译
                options: {
                    presets: [ // 
                        ['@babel/preset-env', {  // @babel/preset-env 根据指定的执行环境提供语法装换,也提供配置 polyfill。
                            // "useBuiltIns": false, // 不对 polyfill 做操作。
                            "useBuiltIns": "entry" // 根据配置的浏览器兼容版本范围,引入浏览器不兼容的 polyfill
                        }]
                    ]
                }
            }
        }]
    }

现在项目支持es11语法了,可以试试,如果不行,看看和实例代码的项目有什么区别。

es6-es11系列7 vue项目中应用es11语法

准备环境

先安装vue-cli脚手架工具 v4.3.1
npm i -g @vue/cli@4.3.1
建议安装两个vscode的插件
vetur 代码提示
eslint 代码检查
免费接口Moke工具
https://jsonplaceholder.typicode.com/
选中页面下面的users https://jsonplaceholder.typicode.com/users

1 使用async/await和解构的方式让代码更简洁,例如

async created(){
    const {data} = await axios.get('https://jsonplaceholder.typicode.com/users')
    this.userList = data
}

2 使用proxy 实现用户列表升序,降序排列,其实这个功能直接用vue的computed也能实现

   async created () {
    // 源数据      
    const { data } = await axios.get('api/getList')
    // 在this下定义代理
    this.proxyData = new Proxy({}, {
      get (target, key) {
        // 当asc属性被获取时执行下面的逻辑,返回一个升序的数组
        if (key === 'asc') {
          // [].concat() 相当于clone了新数组
          return [].concat(data).sort((a, b) => a.name > b.name ? 1 : -1)
        }
        if (key === 'desc') {
          return [].concat(data).sort((a, b) => a.name < b.name ? 1 : -1)
        }
      }
    })
  },
  methods: {
    asc () {
      // 每次按钮点击的时候会获取proxyData下的asc属性
      this.userList = this.proxyData.asc
    },
    desc () {
      this.userList = this.proxyData.desc
    }
  }

3 多图片上传到云存储 阿里云oss

先去阿里的oss申请账号并开通oss服务,因为是后付费,我们把上传完的资源删掉后就不会产生费用了,也没多少钱,放心开通就好。
进入到控制台:https://oss.console.aliyun.com/overview 新建一个Bucket,表达全部使用默认选项即可。
默认上传接口会有跨域限制,可以在Bucket管理中的权限管理-跨域设置中创建一条规则,来源处填入* 来允许任意源
上传后的文件默认是私有阅读权限,访问时会报403错误,所以我们还要在权限管理-读写权限,设置公共读
在项目中安装oss的依赖包 npm i --save ali-oss

初始化sdk的配置

import OSS from 'ali-oss'
// 这两个id在阿里云后台的头像弹出菜单的accesskey管理中去设置
const AccessKey = {
  accessKeyId: 'LTAI5tHyQ1HM3GL213fipXS4jHqc',
  accessKeySecret: 'Y1DAKPpLENN8dfB4wlaJpP1yy4MuvYruE'
}
const client = new OSS({
  // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
  region: 'oss-cn-hangzhou',
  // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
  accessKeyId: AccessKey.accessKeyId,
  accessKeySecret: AccessKey.accessKeySecret,
  // 从STS服务获取的安全令牌(SecurityToken),我们测试用不上这么复杂,真实的项目需要用这种更安全的方式。
  // stsToken: 'yourSecurityToken',
  // refreshSTSToken: async () => {
  //   // 向您搭建的STS服务获取临时访问凭证。
  //   const info = await fetch('your_sts_server')
  //   return {
  //     accessKeyId: info.accessKeyId,
  //     accessKeySecret: info.accessKeySecret,
  //     stsToken: info.stsToken
  //   }
  // },
  // 刷新临时访问凭证的时间间隔,单位为毫秒。
  refreshSTSTokenInterval: 300000,
  // 填写Bucket名称。
  bucket: 'testosslxf'
})

代码参考:





es6-es11系列6 ES10-ES11

ES10 对对象的扩展 Object.fromEntries

Object.fromEntries和Object.entries作用正好相反
比较常用的场景就是把Map结构转换为普通对象,例如
const obj = Object.fromEntries(new Map())
还有,比如我们要对对象进行过滤的时候,可以先把对象转为键值对的数组,使用数组的各种方法后再转回对象,例如:

const course = {
    math: 80,
    english: 85,
    chinese: 90
}
const resArr = Object.entries(course).filter(([key, val]) => val >80)
const resObj = Object.fromEntries(resArr)
console.log(resObj)

ES10 对字符串新增扩展trimStart、trimEnd

顾名思义去掉字符串前面的空格可以使用str.trimStart,同时废除之前的标准trimLeft
trimEnd同理,前后都去使用tirm()

ES10 数组增加flat和flatMap方法

let arr = [1, 2, [3, [4]]] // 这是一个多维数组
arr.flat(1) // 1代表拍平的深度,如果不确定层级深度值可以传入Infinity

使用flatMap可以拍平 arr.map()后的结果

ES10 对Function.prototyp.toString()进行了修订

可以输出注释和空行空格了
例如:

function foo (){
    // es10可以打印注释了
    console.log(1)
}
console.log(foo.toString())

ES10 的try…catch可以省略catch的参数了

之前的catch后面必须要写(e),现在可以省略了,例如
try{…}catch{…}

ES10对JSON扩展superset和stringifiy增强

JSON的superSet就是超集的意思,其实就是支持了字符串里面的特殊字符例如:\u2029,例如下面的代码es10之前会报错

eval('var str="xx"; \u2029 function foo(){return str}')
foo()

eval函数是用来执行一段字符串格式的js代码
之前的JSON.stringify()的取值范围是0xD800~0xDfff
现在针对特殊字符不会出现乱码的bug了

ES10 Symbol的扩展Symbol.prototype.description

const s = Symbol('abc')
console.log(s.description) // adc
s.description = 'foo' // 无效,因为是只读属性

ES11 字符串增加String.prototype.matchAll()

比如有一个场景,我们要从一段html字符串中匹配所有div内的内容放到一个组数里面,
可以使用如下几种方式

const str = `



    
    
    
    Document


    
div1的内容
   
div2的内容
   
div3的内容
` // 最基本的方式使用exec function selectDiv (regExp, str){     let matches = []     while(true){         const match = regExp.exec(str)         if(!match){             break         }         // exec方法会把正则中小括号内匹配的内容放到下标为1的key中         matches.push(match[1])     }     return matches } const regExp = /
(.*)<\/div>/g console.log(selectDiv(regExp, str))  // ['div1的内容', 'div2的内容', 'div3的内容'] // 2 使用match的方式,但是结果不是我们想要的 console.log(str.match(regExp)) // ['
div1的内容
', '
div2的内容
', '
div3的内容
'] // 3 使用replace实现 function selectDivWithReplace (regExp, str){     let matches = []     // replace 的第2个参数可以是个函数,函数的第2个参数和exec类似会返回第1个字表达式的匹配结果     str.replace(regExp, (all, first) =>{         matches.push(first)     })     return matches } console.log(selectDivWithReplace(regExp, str)) // 4 使用matchAll的方式 function selectDivWhitMatchAll(regExp, str){     let matches = []     for(let match of str.matchAll(regExp)){         matches.push(match[1])     }     return matches } console.log(selectDivWhitMatchAll(regExp, str))

ES11新特性 动态导入或者叫按需导入 Dynamic import()

我们可以根据需要的时候再import模块,例如这样:

document.querySelector('#btn').addEventListener('click', () => {
    // 按钮被点击后再引入ajax模块
     import('./ajax').then(m => {
         // 因为模块默认导出到了default上,所以这样写
        m.default('api/getList').then(res => {
            console.log(res.data)
        })
    })
})

vue的路由懒加载也是同样的道理

const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
  routes: [{ path: '/users/:id', component: UserDetails }],
})

ES11新增数据类型 bigInt

以后大数之间的运算更方便了,看一下例子:
定义大数的两种方式

const bigInt = 9007199254740992n // 直接在数字后面写n
const bigInt2 = BigInt(9007199254740992n) // 使用BigInt方法

然后再把2个相乘

// 如果不用大数类型结果是这样,科学计数法不直观
console.log(9007199254740992 * 9007199254740992) // 8.112963841460668e+31
// 使用大数类型后可以正常显示结果了
console.log(9007199254740992n * 9007199254740992n) // 81129638414606681695789005144064n
// 把n去掉可以直接转换为字符串方法toString
console.log(81129638414606681695789005144064n.toString()) //81129638414606681695789005144064

ES11 新增Promise.allSettled()

settled – 稳定的,固定的
与all的主要区别就是allSettled可以在就算有一个promise失败了也能够得到返回结果,然后在then方法里面去写业务逻辑

ES11新增globalThis

获取不同环境下的全局对象
node 环境的全局对象是 global

浏览器环境的全局对象是
window 窗口
self 窗口本身
现在可以使用globalThis自动判断当前环境的全局对象了,无需在自己写函数判断了。

ES11 可选链 ?. 和 空值合并运算符 ?? Nullish coalescing Operator

const street = user && user.address && user.address.street  // 旧
const street = user ?. address ?. street // 可选链
cosnt street =user ?. address ?? 'default' // 双问号判断如果前面条件不成立则使用后面的值,类似 ||

es6-es11系列5 ES7 – ES9的扩展

ES7(2016)新增了2个特性includes、幂运算 **

1 includes
includes是数组的实例方法而不是静态方法
基本用法,注意第2个参数,和数据类型

const arr = ['es6', 'es7', [1], 'es8']
console.log(arr.includes('es7', 1)) // 第2个参数可以声明从哪个索引位置开始查找,性能更好
console.log(arr.includes([1])) // false

2 幂运算 **
原来的方法

Math.pow(2,10) // 1024

新的语法

2**10 // 1024

ES8(2017)新增async / await

其实也是Generator的语法糖
太简单了,不做记录

ES8中对对象扩展 values、 entries

es8之前只能通过keys()获取对象的key
现在可以通过Object.values()直接获取valuse了
通过Object.entries()可以获取一个数组

for(let [key, val] of Object.entries(obj)){
    console.log(key)
    console.log(val)
}

ES8新增对象属性描述符 Object.getOwnPropertyDescriptors()

const obj = {
    name:'sam',
    age:18
}

let dest = Object.getOwnPropertyDescriptors(obj)
console.log(dest)

打印出来的对象展开之后每一项的值会得到下面的结果,例如age
age:

configurable: true // 是否可以用delete删除
enumerable: true // 是否可用for...in 循环
value: 18 // 值
writable: true // 是否可修改

设置对象的这些属性其实就是通过之前的defineProperty方法
通过第2个参数Object.getOwnPropertyDescriptors(obj,‘age’) 可以指定要获取哪个key

ES8对字符串扩展的2个方法padStart、padEnd

String.prototype.padStart(targetLength, padString)
padStart的第一参数是填充后的字符串长度,第2个参数是要填充的内容,如果不写会默认使用空格填充,例如

'abc'.padStart(10);         // "       abc"
'abc'.padStart(10, "foo");  // "foofoofabc"
String.prototype.padEnd() 同理

最常用的使用场景就是日期或者价格补0

ES8新增的参数尾逗号支持

允许参数列表最后一行多写一个逗号
例如function(1, 2, ){} 这样的逗号结尾不会报错了

ES9(2018)新增异步迭代

for-await-of
Symbol.asyncIterator
适用于遍历一个由异步函数组成的对象,例如把3个接口请求封装成promise然后放到一个对象中去再按顺序遍历,参考下面的代码:

 const myAsyncIterable = new Object();
myAsyncIterable[Symbol.asyncIterator] = async function*() {
    yield "hello";
    yield "async";
    yield "iteration!";
};

(async () => {
    for await (const x of myAsyncIterable) {
        console.log(x);
        // expected output:
        //    "hello"
        //    "async"
        //    "iteration!"
    }
})();

ES9 正则的扩展

dotAll
以前正则/./ 不能匹配换行\n和unicode(\u{123})等特殊字符,ES9使用/./s 可以匹配所有字符了
复习一下正则的一些操作符
g global 全局
i ignore 忽略大小写
m mult line 多行匹配
y 粘性
u unicode
s dotAll 任意单字符

具名组匹配

const exec =/(\d{4})-(\d{2})-(\d{2})/.exec('2022-05-11')
const groups = /(?\d{4})-(?\d{2})-(?\d{2})/.exec('2022-05-11').groups
console.log(exec)
console.log(groups)

后行断言
先复习一下什么是先行断言

const str = 'ecmascript' // 这里如果是ecmaxxscript的话就不会符合下面的正则了
//  匹配一个字符串包含ecma但是后面必须是script
console.log(str.match(/ecma(?=script)/)) // ['ecma', index: 0, input: 'ecmascript', groups: undefined]

同样的例子,后行断言如下
// 匹配一个字符串包含script但是前面必须是ecma

console.log(str.match(/(?<=ecma)script/))

ES9 对象的扩展Rest & Spread

// 1 克隆
const obj2 = {
    ...obj1
}
// 2 合并
const obj3 = {...obj1, ...obj2}
// 3 剩余
const {name, age, ...rest} = obj1

ES9 对Promise的扩展 finally()

无论是成功then还是失败catch都会执行finally方法

es6-es11系列4 异步编程

js 先执行同步任务,异步任务会先放入event table内再经过event queue排队执行

AJAX原理

先创建一个AJAX对象通过XMLHttpRequest
通过open方法打开一个连接
通过send发送请求
通过onreadyStatechange事件监听服务器的返回数据

Promise

Promise内的代码在new的时候也是同步代码,例如下面的情况会先输入1再输入2

let p = new Promise((resolve,reject) =>{
    console.log(1)
    resole()
})
console.log(2)
p.then() // 3

Promise的状态不可以逆,例如resole之后就不能再执行reject了
Promise的常用静态方法,无需new操作
Promise.resolve // 应用场景,简单返回一个结果为字符串的Promise对象
Promise.reject // 应用场景,简单返回一个结果为字符串的Promise对象
Promise.all // 应用场景,遍历图片数组,全部上传成功后提示用户完成
promise.race // 应用场景图片加载超时

Generator

Generator函数设计的意思重点是把异步操作放到yeild表达式中,等调用next之后再执行,也是解决回调地狱的一种方案,其实后面es7的async await更好
生成器函数最基本的用法

// 定义个生成器函数,一般会在function后面加上一个星号来表示
let foo = function* () {
    for (let i = 0; i < 3; i++) {
        yield i
    }
}
// 将生成器函数肤质给一个变量,注意此时生成器函数内部的for循环并不会执行,可以在yield i 上面打印个log测试一下
let f = foo()
// 调用f下的next()方法,最后一次因为循环结束,所以没有值被返回
console.log(f.next()) // {value:0, done:false}
console.log(f.next()) // {value:1, done:false}
console.log(f.next()) // {value:2, done:false}
console.log(f.next()) // {value:undefined, done:true}

Generator不能被当做构造函数的方式被使用,换句话说就是不能被new,会报错foo is not a constructor
yield 关键字只能在Generator函数内部使用。例如将上面的for循环改成forEach则会因为作用域的问题报错
一个最典型的应用场景,写一个函数用来生成7得倍数

function* gen(i = 0) {
    while (true) {
        if (i % 7 === 0) {
            yield i
        }
        i++
    }
}
let g = gen()
console.log(g.next()) // 0
console.log(g.next()) // 7
console.log(g.next()) // 14

解决ajax的回调地狱场景,例如有三个接口有依赖关系,常规的写法是嵌套的方式把下一个接口写到回调函数中,使用生成器可以用下面这种写法

function request(url,data = 0) {
    // 使用setTimeout来比喻ajax
    setTimeout(function () { 
        reportData.next(data + 1) // 每次都根据上一次的返回结果去请求下一个接口
    }, 1000)
}

function* gen() {
    let res1 = yield request('api/1')
    console.log(res1) // 1
    let res2 = yield request('api/2',res1)
    console.log(res2) // 2
    let res3 = yield request('api/3',res2)
    console.log(res3) // 3
}

let reportData = gen()
reportData.next() // 开始第一次

Iterator

迭代器是一种接口机制,为不同的数据结构提供统一的访问机制
主要服务与for...of,让步支持遍历的数据结构可遍历
先手写一个迭代器,感觉和Generator非常像

// 手写一个迭代器
function myIterator(arr) {
    let index = 0
    return {
        next() {
            // 这里或者用循环的方式写也可以
            return index < arr.length ? {
                value: arr[index++],
                done: false
            } : {
                value: undefined,
                done: true
            }
        }
    }
}
let it = myIterator ([1,2,3])
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())

一个可迭代的对象,它的属性中必需要有一个Symbol(Symbol.iterator)属性,可以打印一个数组对象然后看看它的Prototype下面属性值Symbol(Symbol.iterator)
例如:

let arr = [1,2,3]
let it = arr[Symbol.iterator]() // 因为数组Prototype下有Symbol.iterator这个迭代器
console.log(it.next())
console.log(it.next())
console.log(it.next())

原生具备Iterator接口的数据结构
Array
Map
Set
String
TypedArray
arguments对象
NodeList对象

可迭代协议: Symbol.iterator

迭代器协议:return { next(){ return{value, done} }
例子:为一个不可迭代的对象添加迭代协议

// 一个不能迭代的对象
let course = {
    allCourse: {
        frontEnd: ['es', 'mp', 'vue'],
        backEnd: ['java', 'python'],
        webapp: ['android', 'ios']
    }
}
// 自己为这个对象实现一个迭代器协议
course[Symbol.iterator] = function* () {
    let allCourse = this.allCourse
    let keys = Reflect.ownKeys(allCourse)
    let values = []
    while (true) {
        if (!values.length) {
            if (keys.length) {
                values = allCourse[keys[0]]
                keys.shift()
                yield values.shift()
            } else {
                return false
            }
        } else {
            yield values.shift()
        }
    }
}
// 可以用for...of遍历了
for(let c of course){
    console.log(c)
}

模块化

CommonJs :基于Node.js
AMD:require.js 依赖前置
CMD::sea.js 就近原则
UMD:集结了 CommonJs、CMD、AMD 的规范于一身
ES6:2015年原生js提供的模块化规范

关键字
export
import
as
export default

es6-es11系列3 对象

面向对象和面向过程

面向过程,考虑的是需求要拆解多少个步骤来实现;
面向对象,考虑的是这个需求里面有多少个对象,他们有哪些属性是必须的,行为之间的关系是如何的;
javascript 准确的说是基于对象(object- based)的语言

类与对象

类是对象的模版,定义了同一组对象共有的属性和方法,类可以有父类和子类。而对象就是对类进行实例化之后的结果,换句话说对象就是我们new出来的的实例。
构造函数内如果定义方法的话,最大的问题就是每次都会执行new Function的操作
例如:this.showName = function(alb){return a+b} 就等于 this. showName = new Function(a, b, ‘return a+b’)
正确的做法是应该把方法定义在原型(prototype)上。

静态属性和静态方法使用场景

比如定一个instanceCount的静态变量
再定一个getInstanceCount的静态方法
在constructor 中行进this.instanceCount ++
这样每次类被实例化的时候instanceCount的值就会+1,通过getInstanceCount则可以获得当前类已经被实例化了多少次

ES5 继承的几种方式

只继承父类的属性(构造函数继承),在子类的构造函数中调用父类的call方法把this传过去,例如

function Dog(name, color){
	Animal.call(this, name)
}

如果要继承父类的方法需要把子类的原型指向父类实例,并且把原型的构造函数指回来,这种方式叫原型继承,例如:

Dog.prototype = new Animal()
console.log(Dog.prototype.constructor) // function Animal() {this.name = 'animal'}
Dog.prototype.constructor = Dog // function Dog(name, color) {Animal.call(this, name)}

如果将上面的两种方法组合起来使用,则可以同时继承属性和方法,所以叫组合继承

ES6的继承

es6 使用extends继承class
在子类的constructor中使用super(name)的方式继承父类的属性,类似es5的构造函数继承
es6中定义顶层的属性的方式使用get语法 ,例如

get sex(){
	return ‘male’
}
let p1 = new Person(‘xx’)后

通过p1.sex获得
这种顶层属性不能被外部直接修改,比如p1.sex=111会报错
如果要改变sex的值,则需要使用set语法
优点是可以在属性设置和获取的时候做前置的拦截操作,例如:

constructor(){
	this._sex = -1
}
get sex(){
	if(this._sex ===1) return ‘male’
 	if(this._sex === 0) return ‘female’
}
// sex的值仅接受0和1
set sex(val){
	if(val ===0 || val ===1) this._sex = val
}

class只能用static定义的静态方法,不能定义属性,父类的静态方法可以被子类调用,但是不可以在实例下调用。
如何非要在类上面定义静态属性,可以直接写Person.count = 5 但是不推荐

Symbol

Symbol不是对象,不能被new,也不能添加属性和方法。

let s1 = Symbol(‘foo’) // 参数可以添加描述,可以调用s1.description来输出描述
let s2 = Symbol(obj) // 如果参数是对象则会自动调用对象下的toString(),可以可以手动在对象下面自定义一个toString方法让它自动被调用

如果使用Symbol.for定义的Symbol则可以相等。
let s1 = Symbol.for(‘foo’) 在全局定义个key为foo的Symbol,可以使用Symbol.keyfor(s1) 打印Symbol全局的key值

使用场景一,解决对象的key重名后覆盖问题

const stu1 = Symbol(‘tom’)
const stu2 = Symbol(‘tom’)
const data = {
	[stu1]: {address:’xxx’},
	[stu2]: {address:’xxx’},
}

场景二,使用Symbol来模拟私有属性

const s1 = Symbol(‘a’)
constructor(){
	this[s1] = 111
}
for in ,Obejct.keys(),Object.getOwnPropertySymbols都不能遍历Symbol属性
只有 用for(let key of Reflect.ownKeys(user))  可以

场景三,魔术字符串
const shapeType = {
	triangle: Symbol(), // 无需给定具体的字符串,比如:’triangle’
	circle: Symbol()
}
switch (shape){
	case shapeType.triangle:
	case shapeType.circle:
…
}

SET

基本语法

let s = new Set([1,1,2]) // [1,2]
s.add(5).delete(2).clear().size
s.has(1) // false

set 可以被forEach和for of遍历
求数组的交集

let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(s1.filtter(item => s2.has(item)))

weakSet // 只能添加对象

let ws = new weakSet()
ws.add(1) // 报错

weakSet不能使用forEach
弱引用,不会被垃圾回收机计数,当引用的对象消失后weakSet也会消失

map

map的key 跟对象不同,可以传对象和数组
常用的方法
set
get
has
size
delete
创建map的基本语法
new Map([‘name’,’tom’],[‘age’,12])
使用forEach for of 进行遍历

map优势
size has 方法更便捷性能更好 key不会跟原型的属性冲突
weakMap 的key只支持引用类型 不能遍历也没有size方法

应用场景

let el = document.get elements by tag name()
let els = new map()
els.set(el,’desc’)

用map存dom元素用key来遍历管理很方便
由于弱引用的机制 还能防治内存泄露

字符串

es5的unicode 取值范围是0000-ffff
es6 使用/u{}的方式增加了取值范围
\hhh 8进制字符
\x12 16进制字符
for of 遍历字符串

模版字符串可以直接换行 无需写+和\n
模版字符还支持函数方式,例如foo’aaa${123}bbb’

es6新增的方法
includes
startsWith
endsWith
repeat(7)

正则表达式

es5中正则中的修饰符
i 忽略大小写
m 多行
g 全局

es6新增两个修饰符
y 粘连修饰符
匹配失败后会返回到起始位置

u unicode 修饰符

数值

b0和 0o来表示十进制和8进制
5/0 会得到无穷大 infinity
‘a’ / 5 会得到 NaN not a number
Number.isFinite() 传入 字符串或布尔时也会返回false
Number.isNaN 注意此方法通常判断值是否是一个数字
es6中会把一些全局的方法放到对应的模块中,例如parseInt parseFloat
Number.isInteger() 是否是整数
Number.trunc 取整
Math.sign 判断是正数还是0还是负数
Math.cbrt 立方根

代理 Proxy

es5中对对象进行拦截使用 Object.defineProperty()
使用Proxy拦截对象,实现私有属性(下划线开头)禁止访问

let user = {
	name:’xxx’,
	age: 18,
	_gender: ‘male’	
}
user = new Proxy(user,{
	get(target, key) {
		if(key.startsWith(‘_’){
			throw new Error(‘不可访问’)
		} else {
			return target[key]
		}
	},
	set(target, key, val) {
		if(key.startsWith(‘_’){
			throw new Error(‘不可访问’)
		} else {
			return target[key] = val
		}
	}
	 has (target, key) {
                    if(key[0] === "_") { // key.startsWith(‘_’)
                        return false;
                    }
                    return key in target;
         }
	deleteProperty(key){
		if(key.startsWith(‘_’){
			throw new Error(‘不可删除’)
		} else {
			delete target[key]
			return ture	
		}
	}
	ownKeys(target){
		return Object.keys(target).filter(key => !key.startsWith(‘_’))
	}
})
使用Proxy拦截函数调用
let sum = (…args) =>{
	let num = 0
	args.forEach(item => {
		num += item
	})
	return num
}
sum = new Proxy(sum, {
	// 当前函数,上下文this,参数
	apply(target, ctx, agrs) {
		return target(…args) * 2
	}
})
// 使用Proxy拦截类的构造函数
let User = class {
	constructor (name){
		this.name = name
	}
}
User = new Proxy(User, {
	// 当前类,参数,目标类
	constructor(target, args, newTarget){
		return new target(…args)
	}
})

反射 Reflect

Reflect 设计的目的
1. 将Object属于语言内部的方法放到Reflect身上
2. 修改某些Object方法的返回结果
3. 让Object操作变成函数行为
4. Reflect对象和Proxy的对象方法一一对应