文章目录
ES6 概述
-
ECMAscript、JavaScript、nodeJS,他们的区别是什么?
ECMAscript:简称ES, 是一个语言标准(循环、判断、变量、数组等数据类型),理论上可以在任何终端实现
JavaScript:运行在浏览器端的语言,该语言使用ES标准, es+webAPI(dom+bom) = JavaScript
nodeJS:运行在服务器端的语言,该语言使用ES标准。es+nodeAPI = nodejs
无论javascript,还是nodeJs,他们都是ES超集(super set) -
ECMAscript有哪些关键的版本?
ES3.0:1999
ES5.0:2009
ES6.0:2015,从该版本开始,不再使用数字作为编号,而使用年份
ES7.0:2016 -
为什么ES6如此重要?
ES6解决了无法开发大型应用的语言层面的问题.
-
如何应对兼容性问题?
-
学习本课程需要的前置知识有哪些?
html+css、JavaScript(es5)
块级绑定
ES6不仅引入let关键字用于解决变量声明的问题,同时引入了块级作用域的概念
块级作用域,代码执行时遇到花括号(判断,循环,函数, 手动写{}),会创建一个块级作用域,花括号结束,块级作用域结束
使用var声明变量的问题
- 允许重复的变量声明:导致数据被覆盖
- 变量提升:怪异的数据访问,闭包问题
- 全局变量挂载到window对象:全局对象成员污染问题
使用let声明变量
可以很好的解决var声明变量的问题
-
let声明的变量不会挂载到全局对象
-
let声明的变量,不允许在当前作用域重复的声明
(不同的作用域可以声明同名变量)
在块级作用域中使用Let定义的语言,不允许在外部作用域中使用 -
使用let不会有变量提升,因此,不能在定义之前使用它
底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到’暂时性死区’,如果访问的变量位于暂时性死区,则会报错:‘cannot access ‘identify’ before initialization’,当代码运行到变量的声明语句时,会将其从暂时性死区中移除。
关于循环的特殊处理
在循环中,let会特殊处理,用Let声明的循环变量,每次进入循环,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)
在循环中,使用let声明的循环变量,在循环结束后,会被销毁
使用const声明变量
const和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值.
实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不被篡改(就根本不能赋值,赋同样的值也不行)
原因:
- 根据经验,开发中的很多变量,都是不会更改,也不应该更改的
- 后续的很多框架或者第三方js库,都要求数据不可变,使用常量可以一定程度上保证这一点
注意的细节:
-
常量不可变,是指声明的常量(如果是对象的话就是一个引用,也就是一个地址,该地址指向具体的数据房间)的内存空间不可变,并不保证内存空间指向的内存不能改变(对象内具体的数据是可以更改的)
-
常量的命名
- 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距离,通常,该常量的名称全部大写,多个单词之间使用下划线进行分割
- 普通的常量,看常量名不确定是否可变,使用普通小驼峰命名
-
在for循环中,循环变量不可以使用常量,for in循环可以,因为for(const prop in obj)中的prop不会改变,而且每次循环的时候都是新的块级作用域,也就是新的prop
字符串和正则表达式
更好的Unicode支持
早期,由于存储空间宝贵,Unicode使用16位二进制来存储文字(全世界的文字),我们将一个16位的二进制编码叫做一个码元(code unit)
后来,由于硬件的发展,Unicode对文字编码进行了扩展,将某些文字扩展到了32位(占用两个码元,一个码点),并且,将某个文字对应的二进制数字叫做码点(code point)
const text = '𠮷';
ES6为了解决这个困扰,为字符串提供了方法: String.prototype.codePointAt(index), 用来得到第index个码点
同时,es6为正则表达式添加了一个flag:u, 如果添加了该配置,匹配的时候根据码点匹配,而不是根据码元
//char[index]是不是32位的
function is32bit(char, index) {
return char.charCodeAt(char, index) > 0xffff; //第一位码点是否大于16位,大于32位的话也就是用码点表示
}
function getTextLengthOfCodePoint(text) {
let len = 0;
for (len i = 0; i < text.length; ++i) {
if (is32bit(text, i)) {
i++; //跳过下一次循环
}
len++;
}
}
字符串的API
字符串的实例的方法
- Includes
判断字符串中是否包含指定的子字符串
- startsWith:
判断字符串中是否以指定的字符串开头
- endsWith
判断字符串是否以指定的字符串结尾
- repeat
将字符串重复指定的次数,然后返回一个新的字符串
正则中的粘连标记
标记名:y
含义:匹配时,完全按照正则对象中的lastIndex位置(默认位0)开始匹配,并且匹配的位置必须在lastIndex位置
模板字符串
解决字符串的换行和字符串与变量的拼接
将引号换为 符号,内部拼接变量的话使用
${}
` ,内部填写表达式
模板字符串标记: 在模板字符串书写之前,可以加上标记:
标记是一个函数,函数参数如下
- 参数1:被插值分割的字符串数组
- 后续参数:所有的插值
- 自定义标记
let like1 = 'music';
let like2 = 'basketball';
let result = myTag `我喜欢${
like1},也喜欢${
like2}`;
//myTag是一个函数,需要自己声明
function myTag(parts) {
const restArugs = Array.prototype.slice.apply(arguments).slice(1); //该数组是插值数组
let resultStr = '';
// parts.length = restArugs.length + 1;
for (let i = 0; i < restArugs.length; ++i) {
resultStr += `${
parts[i]}${
restArugs[i]}`;
if (i === restArugs.length - 1) {
resultStr += parts[i + 1];
}
}
return resultStr;
}
- String.raw
加上该标记的模板字符串内部的换行符,制表符等会被当作普通的字符来处理, 但是变量的插入依然生效
由于可以自定义模板字符串的标记,所以使用的时候也可以对内部拼接的变量做一些安全处理
例如将变量中的<>进行编码
function safe(parts) {
let resultStr = '';
const restArugs = Array.prototype.slice.apply(arguments, 1);
for (let i = 0; i < restArugs.length; ++i) {
resultStr += `${
parts[i]}${
restArugs[i].replace(/</g,'<').replace(/>/g, '>')}`
if (i === resultStr.length - 1) {
resultStr += parts[i];
}
}
return resultStr;
}
4. 函数
参数默认值
在书写形参时,直接给形参赋值,赋的值即为默认值
这样一来,当调用函数时,如果参数传递为undefined, 它就会使用默认值
如果参数传递了值, 默认值不会赋值,如果参数默认值为函数执行,则函数不执行
参数默认值对arguments的影响
严格模式下,形参和arguments是脱离的(不是一一对应的)
只要给函数加上参数默认值,该函数会自动变为严格模式下的规则(形参和arguments不是一一对应的)
留意暂时性死区
形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区
function test(a = b, b) {
console.log(a, b)
}
test(undefined, 2); //会报错,a在赋值的时候,b还未使用
4.2剩余参数
- arguments的缺陷
- 如果和形参配合使用,容易导致混乱
- 从语义上 ,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实用途
ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组(并不和arguments一样是伪数组,它是真数组)中
语法:
function(...形参名) {
// 所有的实参都在形参名数组中
}
细节
- 一个函数只能写一个剩余参数
- 一个函数,如果有剩余参数,剩余参数必须是最后一个参数
4.3 展开运算符
对数组展开 ES6
- 作为实参, 将需要传入罗列参数的函数传入数组就很方便
与剩余参数不同的是,对数组展开的参数可以放在实参的任何位置(开头,中间,结尾)
2
function sum(...args) {
let result = 0;
for (let i = 0; i < args.length; ++i) {
result += args[i];
}
return result;
}
function getRandomArr(len) {
let arr = [];
for (let i = 0; i < len; ++i) {
arr.push(Math.random());
}
return arr;
}
let numbers = getRandomArr(10);
let result = sum(...numbers); //相当于将Numbers数组一个一个传递进sum函数,剩余参数也即为一个10个数组成的伪数组
- 克隆数组
let arr1 = [1, 2, 3, 4, 5];
let arr2 = [10, ...arr1, 29]; //得到新数组[10, 1, 2, 3, 4, 5, 29]
对对象展开 ES7
let obj1 = {
name: '张三',
loves: 'music',
info: {
weight: 70,
height: 188
}
}
let obj2 = {
...obj1,
// 这就是对象的展开,这句话相当于是name:obj1.name,loves:obj1.love...
elseProp: 'value'
}
//obj2是新的对象,指向的地址并不和obj1相同,但是对象内部也是浅克隆
//如果想要针对内部的info对象,可以将info对象在内部在进行一次展开
let obj3 = {
...obj1,
info: {
...obj1.info
}
}
柯里化
用于固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接收剩余的函数(如果剩余的参数不足够,则返回一个新的固定该剩余参数的函数,如果足够则调用);
function curry(func, ...fixedArgus) {
return function(...remaindArgus) {
const totalArgus = remaindArgus + fixedArgus;
if (totalArgus >= func.length) {
// 参数够了
return func(...totalArgus);
} else {
// 参数不够,继续固定
return curry(func, ...totalArgus);
}
}
}
//函数管道
function pipe(...funcs) {
return val => {
for (let i = 0; i < funcs.length; ++i) {
val = funcs[i](val);
}
return val;
}
/*
return val => {
funcs.reduce((result, cur)=>{
return cur(result)
}, val)
}
*/
}
4.5 明确函数的双重用途
- 构造函数调用
- 直接调用(如果函数中有this对象则this指向的是window)
为了避免调用出错,在函数内部判断是否使用new的方式来调用的函数
- 以前的判断方式
if (!this instance of 函数名