前端最基础的就是 HTML+CSS+Javascript
。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS
),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。
前言BB
放开我,我又要吐槽了。看到这个题目的时候,我十分的头秃。
那么我为什么头秃呢?
- ECMAScript 6(ES2015) 做了大量的更新。
那么多的更新我那说的完。ECMAScript 6 入门 - 阮一峰,这里发一下学习链接,只能说大佬牛逼。 - 2015 年 6 月正式发布。
现在来说都四年过去了。其实大多数人都掌握了。 -
其实还有一个误区不是ES5的都归ES6。
其实想想挺逗的。为什么会有这样一个误区?ES2015出来的时候浏览器厂商支持就不一样,ES2016出来之后支持还是不一样,ES2017、ES2018、ES2019 都是这样。这就出现了一个问题,发布是发布了,但是啥时候支持就不一定了。
这里放出来我前两天才看的文章,他们的更新速度是真的恐怖。
提案流程
从提案到正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。
- Stage 0 - Strawman(展示阶段)
有人提出的想法(任何人都可以向 TC39 委员会提案,要求修改语言标准) - Stage 1 - Proposal(征求意见阶段)
想法公布,进行审查与讨论 - Stage 2 - Draft(草案阶段)
想法完善,初始规范 - Stage 3 - Candidate(候选人阶段)
确定要做,完成规范并在浏览器上初步实现。 - Stage 4 - Finished(定案阶段)
到达这里基本就可以在浏览器用了。将添加到下一个年度版本发布中。IE什么的你就不要和我提了
当前的所有提案,可以在 TC39 的官方网站查看。
Babel 是什么?
Babel 是一个 JavaScript 编译器。将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过@babel/polyfill模块)
- 源码转换 (codemods)
配置文件 .babelrc
{
// presets字段设定转码规则
"presets": ["es2015", "react", "stage-2"],
// 插件
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
执行顺序
- plugins 在 Presets 前运行。
- plugins 顺序是从前往后(transform-decorators-legacy、ransform-class-properties)。
- Preset 顺序是从后往前(stage-2、react、es2015)。
官方预设
官方已经针对常用环境编写了一些 preset:
polyfill
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如 Iterator
、Generator
、Set
、Map
、Proxy
、Reflect
、Symbol
、Promise
等全局对象,以及一些定义在全局对象上的方法(比如Object.assign
、Array.includes
)都不会转码。
所以我们还需要使用 babel-polyfill
。
ES6
后面的东西其实看不看无所谓,想要学习呢,还是看阮一峰大佬的好了。
我这里说一下我认为ES6更新了一些什么东西。
- 标准化(国际化、unicode、规范化、健壮性、跨平台)
- 功能方法(之前用奇奇怪怪的方法实现的功能,这次官方给你了)
- 便捷(脚本语言玩的就是骚气)
let、const、块级作用域、暂时性死区
在之前没有块级作用域的概念(try{}catch(e){}
可以模拟,性能呵呵呵呵)。这次加入了块级作用域的概念,let 变量,const 常量。
// 输出什么?
for(var i = 0; i<4;i++){};
i // 4
// 输出什么?
for(let i1 = 0; i1<4;i1++){};
i1 //Uncaught ReferenceError: i1 is not defined
变量提升与暂时性死区
大家都知道面试的时候,喜欢搞一些花里胡哨的鬼东西。
提升是说,var 会被提升到最前面执行,所以不会出现i is not defined
死区是说,let 定义的变量,不能在声明前使用。
(function(){
console.log(1, i) //1 4
//4来源于上面的测试例子,不然会是下面这个鬼样子
//Uncaught ReferenceError: i is not defined
})();
(function(){
console.log(2, i)
var i = 'lilnong.top' //2 undefined
})();
(function(){
console.log(3, i)
let i = 'lilnong.top' // Uncaught ReferenceError: Cannot access 'i' before initialization
})();
globalThis
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。 - 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。 - Node 里面,顶层对象是
global
,但其他环境都不支持。
所以提供了一个访问顶层对象的属性。
解构、默认值
- 数组解构赋值
let [a, b, c] = [1, 2, 3];
- 对象解构赋值
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }
- 字符串解构赋值
const [a, b, c, d, e] = 'hello'
-
函数参数的解构赋值(这个比较常用)
axios('https://www.lilnong.top/cors/axios-destructuring') .then(({data})=>console.log(data))
// y取默认值,然后入参第一个的默认值是{} function move({x = 0, y = 0} = {}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
// y没有默认值,第一个入参的默认值是{ x: 0, y: 0 } function move({x, y} = { x: 0, y: 0 }) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
字符串的扩展、模板字符串、unicode
字符串扩展
只能说写起来更爽了。然后就是之前的写法其实不是很好。会浪费空间,效率不高。${Math.random()}-${Date.now()}-lilnong.top
unicode 更好的支持、codePointAt、fromCodePoint、normalize
面向国际化,以及 emoji
。JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为2
个字节。对于那些需要4
个字节储存的字符(Unicode 码点大于0xFFFF
的字符),JavaScript 会认为它们是两个字符。
这里有个小插曲,weex 中 Text 组件无法包含 Image 组件,实现流式排列。(富文本组件好像是可以解决)
在实现这个功能的时候,我想到可以把字变成块,然后flex 允许换行。
测试地址,里面用了两种方案来获取大于Oxffff
的字符。
- for of
- codePointAt 判断大于
0xFFFF
,需要跳字节。charCodeAt
是ES5就支持的,只能拿到小于OxFFFF
。
更多的API
includes、startsWith、endsWith、repeat、padStart、padEnd、trimStart、trimEnd、matchAll
随着原生提供的 API 越来越多,我们慢慢的不需要使用 Loadsh 等工具库。
正则表达式的扩展、断言、unicode
后行断言
JavaScript 语言的正则表达式,只支持先行断言(lookahead)和先行否定断言(negative lookahead),不支持后行断言(lookbehind)和后行否定断言(negative lookbehind)。ES2018 引入 后行断言,V8 引擎 4.9 版(Chrome 62)已经支持。
“先行断言”指的是,x
只有在y
前面才匹配,必须写成/x(?=y)/
。比如,只匹配百分号之前的数字,要写成/\d+(?=%)/
。“先行否定断言”指的是,x
只有不在y
前面才匹配,必须写成/x(?!y)/
。比如,只匹配不在百分号之前的数字,要写成/\d+(?!%)/
。
ECMAScript 6 入门 - 后行断言 - 阮一峰
数值的扩展、API、指数运算符
更多的API Number.isFinite()、Number.isNaN()、Number.parseInt()、Number.parseFloat()、Number.isInteger()、Number.isSafeInteger()、Number.EPSILON、Number.isSafeInteger()
函数的扩展、默认值、rest参数、箭头函数、尾调用优化
尾调用优化
我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A
的内部调用函数B
,那么在A
的调用帧上方,还会形成一个B
的调用帧。等到B
运行结束,将结果返回到A
,B
的调用帧才会消失。如果函数B
内部还调用函数C
,那就还有一个C
的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
rest参数
(()=>arguments)(1,2,3) // 输出什么?Uncaught ReferenceError: arguments is not defined
在兼容函数中arguments无法使用
((...args)=>args)(1,2,3) //有输出什么?[1, 2, 3]
我们可以使用...
来把入参收集起来
((a,...args)=>args)(1,2,3) //[2, 3]
甚至我们前面用变量接收,后面整体用...
接收
箭头函数
-
this
指向定义时所在的对象 - 不可以使用
arguments
对象
数组的扩展、展开运算符、API、空位的处理
展开
举一个例子你就知道了。去重,虽然这个方法有局限性吧。[...new Set([12, 2, 3, 12, 31, 2, 11, 2, 1, 2, 1, 21, 1, 2, 3, 1, 23, 12, 3, 12, 31, 11, 1, 2, 2, 31, 2, 3])]
数组的API
Array.from()、Array.of()、Array.prototype.copyWithin()、Array.prototype.find()、Array.prototype.findIndex()、Array.prototype.fill()、Array.prototype.entries()、Array.prototype.keys()、Array.prototype.values()、Array.prototype.includes()、Array.prototype.flat()、Array.prototype.flatMap()、
nodeList = document.querySelectorAll('div');
// ES5的写法
[].slice.call(nodeList);
// ES6的写法
Array.from(nodeList);
对象的扩展、缩写、变量名表达式、展开运算符、API
变量名表达式
当我看到下面的写法的时候,内心是卧槽的。
GET_USER_INFO = 'getBaseUserInfo'
obj = {
[GET_USER_INFO](){// 第一次看到是vuex里面分开定义获取数据
console.log(arguments)
},
'abc.sdf'(){// vue watch监听改变的时候
console.log(arguments)
}
}
API
Object.is()、Object.assign()、Object.getOwnPropertyDescriptors()、Object.setPrototypeOf()、Object.getPrototypeOf()、Object.keys()、Object.values()、Object.entries()、Object.fromEntries()
新的类型、新的全局对象
Symbol、Set、Map、Proxy、Reflect、Promise、Iterator、Generator、async、Class、Module
简直了。太多了