之前使用的修改host的方式,体验下来还是不行。很多梯子也解决不了。
现在有很多平台提供免费github加速服务,比如UC浏览器和有道云笔记都有教育资源加速服务,也很不错,尤其是有道云笔记的教育资源加速,我正在用。
然后今天发现了个很nb的方式。就是使用steam++,不但能加速github还能加速steam的商店,但是也不稳定。
Stay Hungry Stay Foolish
之前使用的修改host的方式,体验下来还是不行。很多梯子也解决不了。
现在有很多平台提供免费github加速服务,比如UC浏览器和有道云笔记都有教育资源加速服务,也很不错,尤其是有道云笔记的教育资源加速,我正在用。
然后今天发现了个很nb的方式。就是使用steam++,不但能加速github还能加速steam的商店,但是也不稳定。
症状:vscode使用vim插件后很卡,尤其是中英文切换的时候
根因:Bracket Pairs Colorizer 2 等等 绘图效果插件 会重制图,导致卡
解决方法:禁用 Bracket Pairs Colorizer 2 , rainbow indent等插件
简单来说webpack的作用就是把我们发环境的代码打包成生成环境的代码
entry 入口的职责是要把什么进行编译
output 出口的作用是编译后成什么样子
Loader 进行文件的转换,作用是文件编译
plugin 文件转换后增强一系列的后续操作,可以理解为管道,不参与文件的编译
mode 开发环境还是生成环境
先放出来最终代码地址,如果你赖的自己敲可以复制对应的代码: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语法了,可以试试,如果不行,看看和实例代码的项目有什么区别。
先安装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
async created(){ const {data} = await axios.get('https://jsonplaceholder.typicode.com/users') this.userList = data }
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 } }
先去阿里的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' })
代码参考:
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)
顾名思义去掉字符串前面的空格可以使用str.trimStart,同时废除之前的标准trimLeft
trimEnd同理,前后都去使用tirm()
let arr = [1, 2, [3, [4]]] // 这是一个多维数组 arr.flat(1) // 1代表拍平的深度,如果不确定层级深度值可以传入Infinity
使用flatMap可以拍平 arr.map()后的结果
可以输出注释和空行空格了
例如:
function foo (){ // es10可以打印注释了 console.log(1) } console.log(foo.toString())
之前的catch后面必须要写(e),现在可以省略了,例如
try{…}catch{…}
JSON的superSet就是超集的意思,其实就是支持了字符串里面的特殊字符例如:\u2029,例如下面的代码es10之前会报错
eval('var str="xx"; \u2029 function foo(){return str}') foo()
eval函数是用来执行一段字符串格式的js代码
之前的JSON.stringify()的取值范围是0xD800~0xDfff
现在针对特殊字符不会出现乱码的bug了
const s = Symbol('abc') console.log(s.description) // adc s.description = 'foo' // 无效,因为是只读属性
比如有一个场景,我们要从一段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()) //81129638414606681695789005144064ES11 新增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])) // false2 幂运算 **
原来的方法Math.pow(2,10) // 1024新的语法
2**10 // 1024ES8(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’) 可以指定要获取哪个keyES8对字符串扩展的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但是前面必须是ecmaconsole.log(str.match(/(?<=ecma)script/))ES9 对象的扩展Rest & Spread
// 1 克隆 const obj2 = { ...obj1 } // 2 合并 const obj3 = {...obj1, ...obj2} // 3 剩余 const {name, age, ...rest} = obj1ES9 对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() // 3Promise的状态不可以逆,例如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 defaultes6-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) // falseset 可以被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 ] = arres5 的数组遍历方法
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) // turelet 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) //16let 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次