ECMAScript
- JavaScript的语言本身
- JavaScript是ECMAScript的扩展语言
- JavaScript能够在浏览器环境中操作dom和bom,在node环境中可以做读写文件的操作
- ECMAScript只提供最基本的语法
只停留在语言层面【约定代码如何编写,如何定义变量函数、循环、分支等语句】,不能完成业务中功能性开发
web环境: JavaScript = ECMAScript + Web Apis( dom + bom )
node环境:JavaScript = ECMAScript + Node Apis( fs + net + etc. )
ES2015 ( ES6 )
解决原有语法不足:let,const提供的块级作用域
增强原有语法:解构,展开,参数默认值,模板字符串
全新增加的对象,全新增加的方法:Promise对象,proxy方法
全新的数据类型 Symbol; 全新的数据结构set,map
let与块级作用域
先声明变量,才能使用变量,不能进行状态提升(如val可以先使用,再声明,可以进行状态提升)
块级作用域中定义的成员,外部不能访问到
const
只读,常量,不能修改(不能重新指向一个新的内存地址,可以修改常量中的属性成员),不能重新赋值(会改变内存指向)
最佳实践:不用var,主用const,辅用let (对于一些需要修改的值)
解构赋值
可以解构数组、对象,并可以对解构出来的属性赋默认值
数组解构:根据数组中的位置解构
对象解构:根据键名去解构,也可以对键名进行重命名
模板字符串
模板字符串可支持换行, 模板字符串变量用${}包裹
模板字符串标签函数
// 使用console.log作为模板字符串的标签
const str = console.log`hello world`; // 输出结果:['hello world']
// 这里输出数组的原因是
// 按照模板字符串中有嵌入的表达式,按照表达式分割过后那些静态的内容分割成的数组
function myTagFunc (strings, name, gender) {
// 这里strings是模板字符串中静态字符串经过动态变量分割的数组
// name,gender是动态变量
return strings[0] + name + strings[1] + gender + strings[2];
}
const name = 'tom';
const gender = 18;
const result = myTagFunc`hey, ${name} is a ${gender}`;
console.log(result); //输出结果:hey, tom is a 18
字符串的扩展方法
includes()
字符串中是否包含
startsWith()
字符串开头是否是
endsWith()
字符串结尾是否是
const message = 'Error: foo is not defined';
message.includes('foo'); // true
message.startsWith('Error'); // true
message.endsWith('.'); // false
参数默认值
带有默认值的参数需要在函数入参的最后
剩余参数
// 对于函数未知个数的参数,以前都是使用arguments对象去接收,arguments对象实际上是一个伪数组
function foo() {
console.log(arguments); // 输出结果:[Arguments] {'0': 1, '1': 2, '2': 3, '3': 4}
}
function foo(...args) {
// ES2015新增 ...操作 ,只能放在形参的最后一位,且只能用一次
console.log(args); // 输出结果: [1, 2, 3, 4]
}
foo(1,2,3,4);
展开数组
const arr = ['1', '2', '3'];
// ES2015之前用法
console.log.apply(console, arr); // 输出结果: 1 2 3
// apply 设置this指向,this指向console,展开数组是arr
// ES2015新增加的...
console.log(...arr); // 输出结果: 1 2 3
箭头函数
箭头函数中没有this的机制,所以不会改变this指向
对象字面量增强
const bar = 'bar';
const obj = {
foo: 123,
bar, // 这里如果键名和变量名一致时,可以这样写,等同于 bar: bar
method () {
console.log(this); // 当前作用域 也就是obj
}, // 等同于 method: function () {}
[bar]: 123, // 计算属性名,可以直接在动态键名外用中括号包裹
}
对象扩展方法
Object.assign(target, source1, source2, …)
一般用这个方法进行对象复制
将多个源对象中的属性复制到一个目标对象中
const source1 = {
a: 123,
b: 123,
}
const target = {
a: 456,
c: 456,
}
const result = Object.assign(target, source1); // assign后面的对象属性覆盖第一个对象属性
// { a: 123, c: 456, b: 123 }
console.log(result === target); // true
Object.is(值1, 值2)
判断两个值是否相等
console.log(0 == false); // true
console.log(0 === false); // false
console.log(+0 === -0); // true
console.log(NaN === NaN); // false
console.log(Object.is(+0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
proxy 代理对象
ES5提供的 Object.defineProperty 监视属性中的对象读写。vue3以前的版本都是通过它实现数据响应,从而完成双向数据绑定
Object.defineProperty 只能监视属性读取和写入;proxy能监视到更多对象操作(如delete操作,对对象中方法的调用)
Object.defineProperty 监视数组只能重写数组的操作方法(大致上通过自定义方法去覆盖掉原本数组上的push,pop,shift等方法,以此劫持对这个方法的调用过程);proxy监视数组可以根据对数组的操作推算出来数组变化地方索引,内容等
proxy是以非侵入的方式监管了对象的读写
const person = {
name: 'name',
age: 20,
}
// Object.defineProperty
Object.defineProperty(person, 'name', {
get() {
return person._name
},
set(value) {
person._name = value
},
})
Object.defineProperty(person, 'age', {
get() {
return person._age
},
set(value) {
person._age = value
},
})
// new Proxy(代理对象,代理处理对象);
const personProxy =new Proxy(person, {
// get 方法监视属性的访问
get(target, property) {
// target 是 代理的目标对象 property 是 我们想要访问的属性名称
// 判断 property在target中是否存在
return property in target ? target[property] : 'default';
},
// set 方法监视 对象中设置属性的过程
set (target, property, value) {
// target 是 代理的目标对象
// property 是 我们要写入的属性名称
// value 我们要写入的属性值
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`);
}
}
target[property] = value;
}
});
personProxy.age = '111'; // 会报错
personProxy.gender = true;
console.log(personProxy.name); // zce
console.log(personProxy.xxx); // default
console.log(personProxy.age);
console.log(personProxy.gender);
delete personProxy.age // 可以删除掉 这个是操作符
Reflect
统一的对象操作API
属于一个静态类,不能通过new方式构建实例对象,只能调用静态类中的静态方法,如:Reflect.get()
Reflect内部封装了一系列对对象的底层操作,有13个静态方法
Reflect成员方法就是Proxy处理对象的默认实现
统一提供一套用于操作对象的API (之前操作对象时有可能使用object上的方法,也有可能用delete、in这种操作符)
const obj = {
name: 'eee',
age: 18,
}
// 在没用Reflect时,对对象进行操作时,可能是对象本身的操作方法,可能是操作符
'name' in obj;
delete obj['age'];
Object.keys(obj);
// 使用Reflect时
Reflect.has(obj, 'name');
Reflect.delectProperty(obj, 'age');
Reflect.OwnKeys(obj);
Promise
解决了传统异步编程中回调函数嵌套过深的问题
class类
独立定义类型
// 以前 想要定义 Person 的类型时,需要定义一个Person的函数作为这个类型的构造函数
function Person (name) {
// 可以通过this去访问当前的实例对象
this.name = name;
}
// 如果我们需要在这个类型所有的实例之间去共享一些成员
// 可以借助函数对象的prototype原型去实现
Person.prototype.say = function () {
console.log(this.name);
}
// class 类
class Person {
// 构造器
constructor (name) {
this.name = name;
}
say () {
console.log(this.name);
},
}
// 还是通过new关键字 创建Person 类型的实例
const p = new Person('tom');
静态方法
实例方法:通过这个类型构造的实例对象去调用
静态方法:直接通过类型本身去调用
关键词 static
class Person {
// 构造器
constructor (name) {
this.name = name;
}
say () {
console.log(this.name);
},
static of (name) {
return new Person(name);
}
}
// 这里调用Person类中的of方法时,不需要通过new关键字去创建实例对象
// 可以直接用一下方式调用
const temp = Person.of('tom');
temp.say();
类的继承
关键词 extends
class Person {
// 构造器
constructor (name) {
this.name = name;
}
say () {
console.log(this.name);
},
}
class Student extends Person {
constructor (name, num) {
// super对象始终指向父类,调用它就是调用父类中的构造函数
super(name);
this.num = num;
}
hello () {
super.say();
console.log(this.num);
}
}
const s = new Student('jack', 100);
s.hello(); // jack 100
set数据结构
常见的是为数组元素去重
// Set是类型,通过new Set去构造实例对象
const s = new Set();
s.add(1).add(2).add(3).add(2); // Set { 1, 2, 3}
// add方法返回集合对象本身,如果add添加已有的值时,s会将其过滤掉
s.size; // 获取集合长度
s.has(100); // 这个集合中是否存在某个值
s.delete(3); // 删除这个集合中指定的值
s.clear(); // 清除这个集合中的全部内容
const arr = [1,1,2,2,3,3,4];
const result = new Set(arr); // Set { 1, 2, 3, 4 }
const result = [...new Set(arr)]; // [1, 2, 3, 4]
Map数据结构
Map可以用任意类型的数据作为键,而对象的键只能用字符串
Symbol
一种全新的原始数据类型
最主要的作用是为对象添加独一无二的属性名
const cache = {};
// a.js
cache['a_foo'] = Math.random();
// b.js
cache['b_foo'] = '123';
// 以前都是约定好每个文件中设置变量名如何设置,不会重复
const s = Symbol();
// 每次调用Symbol都是独一无二的,不会重复
const obj = { [Symbol()]: 123, [Symbol()]: 456 }; // { [Symbol()]: 123, [Symbol()]: 456 }
// 如果想要复用同一个Symbol值,Symbol提供了for方法,是静态方法
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
console.log(s1 === s2); // true
const obj1 = {};
console.log(obj1.toString()); // [object Object] 这里叫做对象的 toString 标签
const obj2 = {
[Symbol.toStringTag]: 'XObject'
};
console.log(obj2.toString()); // [object XObject] 这里叫做对象的 toString 标签
Object.getOwnPropertySymbols(obj); // 获取的全是键名为Symbol值的属性
BigInt
用于存放更长的数字,ES2019中
for…of…
for…in…适合遍历对象,for…of… 适合遍历数组,伪数组
// forEach循环无法终止循环
// forEach想要终止循环,需要借助 array.some() 或者 array.every()
for (const item of array) {
break; // 可以终止循环
}
// 伪数组也可以用for...of...
for (const item of arguments) {
}
// Map也可以用for... of ...
Iterable 可迭代接口
无论什么复杂的数据结构都是实现了统一的接口Iterable所以才能被for…of…循环;
用for…of…的前提就是 实现Iterable接口
// 实现可迭代接口 Iterable
// 实现对象的iterator方法后,对象就可以用for...of...遍历了
// 整个对象 叫Iterable; 带有next方法的叫iterator; next方法里面的叫iteratationResult
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0;
const self = this;
return {
next: function () {
const result = {
value: self.store[index],
done: index > self.store.length,
}
index++;
// return 返回对象是 迭代结果接口
return result
}
}
}
}
迭代器模式
迭代器的意义:对外提供统一遍历接口,让外部不用关心数据内部结构是什么
生成器 generator
生成器函数最大的特点:惰性执行
用生成器是为了 避免异步编程中回调函数嵌套过深,从而提供更好的异步编程解决方案,详情在异步编程源码那块详细描述
生成器函数的应用:发号器,实现对象的iterator方法
// 发号器
function * createIsAdd () {
let i = 0;
while (true) {
yield i++;
}
}
const isAdd = createIsAdd();
console.log(isAdd.next().value); // 1
console.log(isAdd.next().value); // 2
console.log(isAdd.next().value); // 3
console.log(isAdd.next().value); // 4
ES Modules
语言层面的模块化标准
ES2016
数组实例对象的includes方法
const arr = ['foo', 1, 2, 'fjf'];
console.log(arr.includes('foo'));
指数运算符
// 2的10次方
// 以前用法
Math.pow(2,10);
//指数运算符
2 ** 10;
ES2017
Object.values(对象)
ES2015中的Object.keys返回键名组成的数组,而Object.values返回键值组成的数组
const obj = {
age: '18',
name: '张三',
}
console.log(Object.values(obj)); // ['18', '张三']
Object.entries(对象)
以数组的形式返回对象中所有的键值对
const obj = {
age: '18',
name: '张三',
}
console.log(Object.entries(obj)); // [['age', '18'], [name, '张三']]
// 可以先将对象转化为 以数组形式返回对象所有的键值对
// 然后用for...of...去遍历
for ( const [key, value] of Object.entries(obj)) {
console.log(key, value); // age 18
}
Object.getOwnPropertyDescriptors(对象)
获取对象当中属性的完整信息
ES2015过后可以给对象定义get或者set属性,这种属性是不能通过Object.assign方法去完全复制的,
配合ES2015对对象定义get,set使用
const p1 = {
firstName: 'jintian',
lastName: 'tianqi',
get fullName () {
return this.firstName + this.lastName;
}
}
const description = Object.getOwnPropertyDescriptors(p1);
字符串的原型方法 padStart
字符串填充方法,用给定的字符串去填充目标字符串的开始位置
字符串的原型方法 padEnd
字符串填充方法,用给定的字符串去填充目标字符串的结束位置
const msg = '今天是个好日子';
msg.padEnd(16, '-'); // 如果msg字符串没有16位时,从尾部缺少几位用几个'-'填充
在函数参数中添加尾逗号
function get_1(age, name, ) {
}
async / await
解决了异步回调函数嵌套过深的问题,使得代码更加简洁易读。