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的对象方法一一对应

es6-es11系列1-2基本语法

EECMA 组织每年都会更新一个版本
Es6对应 es2015
Es11 对应 es2020
以此类推

比较典型的es6新特性

可选链

list  =  res?.data?.list

使用SymboSymbo 消除魔法字符

const ls ={
        running:Symbol(),
        finish:Symbol ()
}
function  a (state){
        switch (state)
        case ls.running:
          console.log  r
        case ls.finish:
        ...
a(ls)

类数组转数组

Array.from(noteList)

利用nrm命令 快速切换源地址

npm i -g nrm  
nrm ls
nrm test taobao 
nrm  use  taobao

基本语法

作用域

delete  只能删除属性  不能删除变量
let定义的变量不会像var那样被挂到window对象上
let  不会像var那样重复声明
let  不存在变量提升 ,变量定义之前不能被打印出来
let  会形成暂时性死区,变量未声明之前不能被使用
let有块级作用域

es5 中如何定义常量

使用Object.definepropert方法
Const定义的引用类型  可以被添加新的属性
可以通过object.freeze  冻结对象,仅能对对象做浅冻结
结构赋值可以设置默认值,例如  let [  a,  b,  c  =2 ]  =  arr

es5 的数组遍历方法

forEach 方法不支持  break和  continue  无法终止循环
map 可以返回数组中的每一项组成一个新 的数组  例如  item +  1
filter  返回满足条件的数组项  例如  return item >2
some  判断数组中的值只要有一个满足条件则返回ture
every  要求没一项都满足条件才返回ture
reduce 第一个参数是上次的返回值,第二个参数是当前的值,适合做累加,求最大值,去重
for in 遍历数组时会把隐式原型的值也遍历出来

es6 数组新增特性

find  可以返回数组中满足条件的数组项
findIndex  返回满足条件的数组项下标
for  of  可以遍历数组的index  和  value
for(  [index,value]  of arr.entries() )
Array.of(1,2,3) //通过参数创建数组 [1,2,3]
arr.copyWithin(1,3) 下标为1的值替换下标为3开始到末尾的所有值
[1,2,3].fill(‘a’,0,2) // [‘a’, ‘a’, 3] 将数组中下标0到2的值填充为 ‘a’
arr.includes(1,2) 数组中是否包含1和2
indexOf 不能检测到 NaN includes则可以

类数组相关

html collectionname: get elements  by tag name
nodelist :   query selection
判断是否是数组
instanceOf array
调用对象的push方法  如果报错则不是数组
arguments 是对象不是数组
es5遍历类数组使用Array. prototype. forEach. call(arguments)
Es6 新增Array.from方法来转换对象为数组

函数的参数
es6的参数可以设置默认值
有默认值的参数建议放到最后,意味着可以省略
函数的length 返回的是为指定默认值的参数的数量
参数作用域的特性,如果参数设置了默认值则会形成作用域

let x=1
fun foo(x, y=x){
    console.log(y) // 2
}
foo(2) 

函数有name属性 如果new Function 则name值为anonymous ,如果使用了foo.bind({}) 则name值为bond

扩展运算符和rest参数

扩展运算符
打散…arr
合并 […arr1,…arr2] 或arr1.push(…arr2)
剩余参数

fn foo(x,...y) // 1 [2,3]
foo(1,2,3)

let [x,...y] =[1,2,3]

箭头函数特性
没有this
不能被new
没有arguments对象,可以使用…args代替

对象的扩展

Object.is(NaN,NaN) // true
console.log(NaN == NaN) // false
Object.is(Obj1,Obj2) // false
console.log(Obj1 == Obj2) // false 

因为引用类型的对象指向的内存地址不一样

Object.assign(a, b)
console.log(‘name’ in Person) // ture
let arr = [1,2,3]
2 in arr // 数组对象的in 判断的是下标处是否有值

对象的几种遍历方式

for(let key in obj){
	let value = obj[key}
}
Object.keys(Obj).forEach(key =>{
	let value = obj[key}
})
Object.getOwnPropertyNames(obj).forEach(key =>{
	let value = obj[key}
})
Reflect.ownKeys(obj).forEach(key =>{
	let value = obj[key}
})

深拷贝和浅拷贝
浅拷贝

JSON.stringify(obj)
JSON.parse(str)

弊端:
Date 对象变为字符串
RegExp、Error 对象变为空对象 {}
函数、undefined、Symbol 属性丢失
NaN、Infinity、-Infinity 变为 null
enumerable 为 false 的属性丢失
循环引用的对象不能正确拷贝

深拷贝网络上自行搜索吧,很常用

判断是对象还是数组 除了instanceOf 也可以用

Object.prototype.toString.call(data).slice(8,-1)

2021年前端基础面试题 18 额外一些面试真题

typeOf 返回的数据类型有哪些
手写深度比较函数,模拟lodash的 isEqual()
string.split()和arr.jion()的区别
数组中哪些方法是纯函数,哪些是非纯函数
数组中的splice和slice的区别,splice会改变原数组的值

网红题目 [1,2,3].map(parseInt) // [1,NaN,NaN]

原因: parseInt(17,2) // NaN, 因为parseInt的第个参数进制基数radix必须是10、8、16、2 parseInt(10,2) // 2
parseInt(10,8) // 8
parseInt(10,16) //16

let a = function(){} 和 function a(){} 的区别

let a是函数表达式,function a是函数声明,函数声明可以在代码执行前预加载,所以在function a上面执行 a()不会报错;

new Object()和 Object.create()的区别

new Object() 等于 let a = {}, 它是有原型属性的 而Object.create()参数中可以指定原型对象,所以当传入null的时候,例如Object.create(null) 它是没有原型属性的。

异步操作的相关题目

let i 
for(i = 1; i<=3; i++){
    setTimeout(function(){
       console.log(i)
    })
} 
// 4 4 4 

解释如果不使用setTimeout结果是 1 2 3,用了之后则会先执行同步任务的for循环,结束之后i的值为4,因为循环了3次,webApi中会往事件队列中放入3个function,所以打印了3次i

作用域相关题目

let a = 100
function test(){
    alert(a)
    a = 10
    alert(a)
}
test()
alert(a)
// 100 10 10

捕获错误的2种方式 try catch和window.onerror

获取url参数的2种方式

老:location.search // '?a=1&b=2' 需要自己实现个方法去获取对应的参数 新:URLSearchParams
let params = new URLSearchParams(location.search)
console.log(params.get('a'))

将url参数解析为js对象

function queryToObj(){
    const res = {}
    cosnt pList new URLSearchParams(location.search)
    pList.forEach((val,key)=>{
        res[key] = val
    })
    return res
}

数组拍平
数组去重
手写深拷贝

RAF( window.requestAnimationFrame)

可以让动画更流畅,比setTimeout更适合做动画,自动控制频率,dom隐藏或窗口切换后会暂停,性能更好 比如我们要做60帧的动画,1秒要用setTimeout执行60次

2021年前端基础面试题 16-17 运行环境、性能和安全

16 运行环境、性能和安全

XSS/XSRF 攻击

预防XSS:替换特殊字符,如< 变为< 可以通过xss工具 npm i xss


17 性能优化

提升加载速度

减少资源体积(压缩代码)
减少访问次数(合并代码、SSR、缓存)
使用更快的网络(CDN)

提升渲染速度

CSS放在header内,js放在body最下面
尽早开始执行js,用DOMContentLoaded触发
懒加载
对dom查询进行缓存
频繁的dom操作合并到一起后统一插入
节流和防抖

页面加载过程

DNS(domain name server) 解析: 域名 – ip
HTTP
生成DOM Tree
生成CSSOM
整合DOM Tree和CSSOM合成Render Tree
遇到script暂停渲染,完成后继续
图片加载不会阻塞render tree

window.addEventListener('load') // 页面全部加载完成后触发,包括图片和视频
window.addEventListener('DOMContentLoaded') //Dom渲染完之后触发,无需等待图片和视频

图片懒加载的方式

将图片地址先写死一个预览的小图地址,而真实的图片地址可以放在例如data-relasrc属性中,当屏幕滚动到需要显示这个图片的位置时,替换src



2021年前端基础面试题 15 开发环境

git使用

假如在a分支上不小心修改了很多文件后发现其实应该在b分支上修改,这时直接切换分支已经不行了,注意新建的文件m不受影响。
可以先执行git stash把已修改的文件暂存起来。切换到b分支后再执行git stash pop把文件取出来

charles使用

手机抓包的过程

webpack

如何创建开发环境和生成环境2套配置
使用模板插件,配置不同的loader,生产环境的文件名[contenthash]

babel

基本的es6转es5配置,使用预设插件

Liunx

mkdir //创建文件夹
touch a.js //新建文件
open . // 打开当前目录
ls ll //显示当前目录下的文件列表
cat index.html //把文件内容打印到控制台
rm -rf abc (r= recurrence f= force) //递归强制删除目录
mv a.html b.txt //修改文件名, 或者移动文件到目标文件夹
cp a.js b.js //拷贝
vimtutor //系统自带的vim教程