ES6
字面量增强
ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。
属性的简写:Property Shorthand
方法的简写:Method Shorthand
计算属性名:Computed Property Names
// 1.属性的简写
var obj = {
name: name
age: age
}
// 增强后
var obj = {
name,
age
}
// 2.方法的简写
var obj = {
foo: function() {}
}
// 增强后
var obj = {
foo() {},
}
// 3.计算属性名
var name = "test";
var obj = {}
obj[name] = "haha"; //如果key是一个变量 不能用.的方式 要使用[]
// 增强后
var obj = {
[name]: "haha"
}
解构
ES6中新增了一个从数组或对象中方便获取数据的方法,称之为解构Destructuring
数组解构
var names = ["a", "b", "c"];
var ages = [1, 2, 3, 4, 5];
// 取出数组中某一个
var [item1, item2, item3] = names;
// 解构出后面两个元素
var [, item2, item3] = names
// 解构出第一个元素,后面的元素放到一个新数组中
var [itemx, ...newNames] = names
// 解构默认值
var [item1, item2, item3, item4 = "d"] = names
console.log(item4)
// 合并两个数组
var arr = [...names, ...ages]
对象解构
var obj = {
name: "why",
age: 18,
height: 1.88
};
var obj1 = {
name: "zhangsan",
address: "深圳"
}
// 对象解构
var { name, age, height } = obj;
// // 解构出某个key,后面的key放到一个新对象中
var { age, ...newObj } = obj;
// // 解构出某个key 并且重新命名
var { name, age: newAge, height } = obj;
// 不用delete 如何删除 对象的某个属性? delete obj.a
var { name, ...rest } = obj // 删除了a , c
obj = rest;
// 合并两个对象 相同的属性会被覆盖
var obj2 = {...obj, ...obj1}
var/let/const
var
- 没有块级作用域,是弱类型,支持变量提升。
- 可以重复声明,没有报错和警告
- ES5中只有全局作用域和函数作用域,大部分人会采用闭包来解决ES5的问题
let
- 只有块级作用域 window无法访问
- 不存在变量提升(解析阶段会被创建出来,但是不能被访问)
- 存在暂时性死区(TDZ)
//"暂时性死区"也意味着**typeof**不再是一个百分之百安全的操作。
//在没有**let**之前,**typeof**是百分百安全的,现在这一点不成立了.
typeof x; //ReferenceError: a is not defined.
let x;
- 不允许重复声明
const
- 只有块级作用域 window无法访问
- 不存在变量提升(解析阶段会被创建出来,但是不能被访问)
- 存在暂时性死区(TDZ)
- 不允许重复声明
- 声明时必须赋值,且后续不允许修改,如果是对象,对象的属性可以被修改。
实际开发如何选择
首先排除var在let和const之间做选择,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
一个是const可以提醒阅读程序的人,这个变量不应该改变;另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,也就是说let和const的本质区别,其实是编译器内部的处理不同.
作用域
在ES6之前,只存在全局作用域和函数作用域。
var text = "全局作用域";
function foo() {
var bar = "函数作用域,只有在这个函数才能访问"
}
在ES6的时候有了块级作用域
{
var x = 123;
let y = 456;
const z = 789;
}
// 只有x 能正常被打印
console.log(x,y,z);
在ES6中,块级作用域对于var是没有作用的,var只有函数作用域和全局作用域。let、const存在块级作用域、函数作用域、全局作用域。块级作用域对于let、const、function、class等等一些是有效的。
{
function test() {
onsole.log("测试块级作用域是否有效")
}
class Person{}
}
test(); // 能够正常调用函数,为什么呢?
let p = new Person(); //Person is not defined
上面说块级作用域对于function有效,那为什么在块级作用域之外能够调用函数呢?
不同浏览器有不同的实现(大部分浏览器为了兼容以前的代码),让我们的function是没有块级作用域的。但是在标准中,块级作用域对于function是存在的。但是如果有某一个浏览器 只支持ES6及以上的代码,这个时候test函数是访问不到的。
块级作用域的补充
const names = [1,2,3,4,5]
// 这里是不能 使用const的 虽然const是块级作用域
// 但是每次在块级作用域里面都在尝试修改 i ---> i++
for(const i = 0; i < names.length; i++){
console.log(names[i]);
}
// {
// i = 0;
// const i = i;
// }
// {
// i++;
// const i = i; // const是没法被修改的,const初始化也必须要有值
// console.log(names[i]);
// }
// {
// i++;
// const i = i;
// console.log(names[i]);
// }
// ...
// for ... of 遍历可迭代对象
// 这里是可以使用 const的
for(const item of names) {
console.log(item);
}
// 块级作用域
// {
// const item = 1;
// console.log(item);
// }
// {
// const item = 2;
// console.log(item);
// }
///.....
字符串模板
在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)
const name = "malong";
const age = 18;
const height = 1.88;
console.log(`my name is ${name}`);
console.log(`我是成年人吗? ${ age > 18 ? '是' : '否'}`)
function foo() {
return "function is foo";
}
console.log(`mu function ${foo()}`);
标签模板字符串的使用
function foo(...args) {
console.log(args);
}
const name = "why";
const age = 18;
foo`Hello${name}Wor${age}ld`;
如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:
模板字符串被拆分了;
第一个元素是数组,是被模块字符串拆分的字符串组合;
后面的元素是一个个模块字符串传入的内容;
函数的默认参数
在ES6之前,我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求
传入了参数,那么使用传入的参数;
没有传入参数,那么使用一个默认值;
// ES5 写法
function foo( x , y) {
x = 20 || x;
y = 30 || y;
console.log(x, y);
}
// ES6 写法
function foo( x = 20 , y = 30) {
console.log(x , y)
}
foo(10,20); // 10 20
foo(); // 20 30
foo(0,"") // es5 有bug es6没有bug
函数的剩余参数
ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
如果最后一个参数是 … 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
// 剩余参数只能放到最后面
function foo( m, n, ...args) {
console.log(m, n)
console.log(args)
console.log(arguments)
}
// args 是一个数组 (ES6新增想要替代arguments)
// arguments 是伪数组
// args只包含那些没有对应形参的实参,arguments 对象包含了传给函数的所有实参
foo(20, 30, 40, 50, 60)
箭头函数
箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象
var foo = () => {
console.log("foo");
}
console.log(foo.prototype); //undefined
var f = new foo(); //TypeError: foo is not constructor
展开运算符
在函数调用时使用
const names = [1,2,3];
function foo(x, y, z){
console.log(x,y,z);
}
// 展开运算符
foo(...names);
在数组构造时使用
const names = [1,2,3];
const address = [1,2,4];
const newObj = [...names , ...address]; // 1,2,3,1,2,4
在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性
const names = [1,2,3]
const address = "test";
// {0: 1, 1: 2, 2: 3, address: 'test', age: 18}
const newObj = { ...names, address, age: 18};
展开运算符浅拷贝
const info = {
name: "why",
friend: {name: "kebi"}
}
const obj = { ...info, name:"coderwhy" };
obj.friend.name = "test";
console.log(info.friend.name); // 会被改变成test
Symbol
Symbol在通常在对象中表示唯一的属性名
// 在对象中,属性名key是唯一的,相同的会覆盖,但是Symbol就是用来创建一个独一无二的值
// 可以让其不被覆盖
const s1 = Symbol("abc");
const s2 = Symbol("bcd");
const obj = {};
// 1.写法1:属性名赋值 , 不能使用.的语法
obj[s1] = "abc";
obj[s2] = "bcd";
//obj.s1 = "abc" 错误
// 2.写法2 Object.defineProperty
Object.defineProperty(obj,s1,{
configurable: true,
writable: true,
enumerable: true,
value: "abc"
})
// 3.写法3 字面量
const info = {
[s1]: "abc",
[s2]: "bcd"
};
// 获取Symbol的key 有固定的方法 使用循环 Object.keys都是获取不到的
const symbolKyes = Object.getOwnPropertySymbols(info);
// symbol 的key
console.log(symbolKyes)
// 遍历info for of遍历的是value ---- for in 遍历的是key
for (const item of symbolKyes) {
console.log(info[item])
}
相同值的Symbol
// 使用for 让描述相同 就可以了
const s1 = Symbol.for("aaa");
const s2 = Symbol.for("aaa");
console.log(s1 === s2); // true
// keyFor 也可以 key要是 Symbol创建出来的
const s3 = Symbol.keyFor(s1);
const s4 = Symbol.keyFor(s2);
console.log(s3 === s4); //true
const s5 = Symbol("aaa");
const s6 = Symbol("aaa");
console.log(s5 === s6); // false
Set
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):
Set常见的属性和方法
size 返回Set中元素的个数。[属性]
add(value) 添加某个Set元素,并返回对象本身。
delete(key):根据key删除一个键值对,返回Boolean类型。
has(value) 判断某个元素是否存在,返回boolean类型。
clear() 清空所有元素,没有返回值。
forEach() 遍历
使用场景:数组去重
WeakSet
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?
区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
无法遍历
常见方法
add(value):添加某个元素,返回WeakSet对象本身;
delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
使用场景
// Stack Overflow
const pwset = new WeakSet();
class Person {
// new的时候会执行constructor里面的代码
constructor() {
pwset.add(this)
}
running() {
if (!pwset.has(this)) {
throw new Error('不能通过其他对象来调用running方法')
}
console.log("running", this)
}
}
var p = new Person();
console.log(p.running())
// 会报错
p.running.call({ })
// 这里为什么使用WeakSet呢?
// 因为WeakSet是弱引用 我们把p = null 后 就会被GC回收
// 如果使用Set 就会是强引用 ,我们把p = null 后 还要 set.delete(p) 才会被GC回收
强引用和弱引用的区别
弱引用
弱引用与强引用相对, 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。因此使用弱引用可以防止内存泄漏。在JavaScript中弱引用的WeakMap和WeakSet。
强引用
JavaScript中最常见的就是声明一个变量并且将一个引用类型数据(对象)赋值给这个变量,这种情况就是强引用。只要该对象还被引用,垃圾回收机制GC就不会回收该对象或者属性。对于一个普通对象,将全部引用该对象的变量关系相应设置为null,便能等待垃圾回收机制GC回收。
map
数据结构Map,用于存储映射关系。
事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);
某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key;对象的key内部会自动toString();
Map常见的属性和方法
size 返回Set中元素的个数。[属性]
set(key, value):在Map中添加key、value,并且返回整个Map对象。
get(key):根据key获取Map中的value。
has(value) 判断某个元素是否存在,返回boolean类型。
clear() 清空所有元素,没有返回值。
forEach(value,key,map) 遍历
使用场景:缓存
const decorator = (fn) => {
const mapList = new Map();
return (x, y) => {
const sum = `${x}${y}`;
if (mapList.has(sum)) {
return mapList.get(sum);
}
const res = fn(x, y)
mapList.set(sum, res);
return res;
}
}
let work = (a, b) => {
return a + b;
}
work = decorator(work)
work(1, 2);
work(1, 2); // 这里实际上用的缓存
work(3, 4);
work(3, 4); // 这里实际上用的缓存
测试
// 对比Object,JavaScript对象的key只能是字符串或者是ES6新增的Symbol
const obj = { name: "why" };
const obj1 = { name: "coderwhy" };
const info = {
[obj]: "zhangsan",
[obj1]: "lisi"
};
console.log(obj); //我们会发现key 被自动toString了,而且也会被覆盖。
// 使用map作为key
var map = new Map();
map.set(obj, "zhangsan"); // 返回整个Map对象
map.set(obj1, "lisi"); // 返回整个Map对象
map.set("1", 2); // 返回整个Map对象
map.set("3", 4); // 返回整个Map对象
// 通过key 获取 value
map.get(obj); // 返回key 对应的 value
// 获取长度
console.log(map.size); // 返回长度
// 通过key 删除
map.delete(obj); // 返回boolean
// 判断key 是否存在
map.has(obj1); // 返回boolean
// forEach 遍历map
map.forEach((value, key, map) => {
console.log(value, key, map);
})
// for...of 遍历
// 结构出来
for (const [key, value] of map) {
console.log(key, value);
}
// for...of 遍历
// 不解构
for (const item of map) {
// 0对应key 1对应value
console.log(item[0], item[1]);
}
WeakMap
WeakMap的key只能使用对象,不接受其他的类型作为key
WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
WeakMap常见方法:
set(key, value):在Map中添加key、value,并且返回整个Map对象
get(key):根据key获取Map中的value
has(key):判断是否包括某一个key,返回Boolean类型
delete(key):根据key删除一个键值对,返回Boolean类型
Weak使用场景(vue3响应式原理)
const obj1 = {
name: "why",
age: 18
}
const obj2 = {
name: "why",
age: 18
}
// 1.创建WeakMap
const weakMap = new WeakMap();
// 2. 收集依赖结构
// 2.1 使用map来收集
const obj1Map = new Map();
obj1Map.set("name", [obj1GetName, obj1SetName])
obj1Map.set("age", [obj1GetAge, obj1SetAge])
weakMap.set(obj1, obj1Map)
// 3.如果obj1.name发生改变
// Proxy/Object.defineProperty
obj1.name = "test";
const targetMap = weakMap.get(obj1);
const fns = targetMap.get("name")
fns.forEach(item => item())
function obj1GetName() {
console.log("obj1GetName")
}
function obj1SetName() {
console.log("obj1SetName")
}
function obj1GetAge() {
console.log("obj1GetAge")
}
function obj1SetAge() {
console.log("obj1SetAge")
}
ES7
includes
const names = ["abc", "cba", "nba", "mba", NaN];
if (names.indexOf("abc") !== -1) {
console.log("包含abc")
}
if (names.indexOf(NaN) !== -1) {
console.log("包含NaN"); //不会打印
}
if (names.includes(NaN)) {
console.log("包含NaN"); //会打印
}
if (names.includes("abc")) {
console.log("包含abc")
}
指数运算
const result1 = Math.pow(3,3);
// ES7
const result2 = 3 ** 3;
ES8
Object.values
之前可以通过Object.keys获取对象(不包括原型)中所有的key,在ES8中提供了Object.values()来获取所有的values。
const obj = {
name: "zhangsan",
age: 18,
address: [1, 2, 3, 4, 5],
friends: {
a: 1,
b: 2,
c: 3
}
}
console.log(Object.keys(obj));
console.log(Object.values(obj));
entries
通过Object.entries 可以获取到一个数组,数组中会存放可枚举属性的键值对数组
const obj = {
name: "zhangsan",
age: 18,
address: [1, 2, 3, 4, 5],
friends: {
a: 1,
b: 2,
c: 3
}
}
Object.defineProperty(obj, "name", {
configurable: true,
enumerable: false,
writable: true,
value: "999"
})
// [["age","18"],["address",[1,2,3,4,5]],["friends",{a:1,b:2,c:3}]]
console.log(Object.entries(obj))
String Padding
某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8中增加了 padStart 和 padEnd方法,分别是对字符串的首尾进行填充的。
我们简单举一个应用场景:比如需要对身份证、银行卡的前面位数进行隐藏:
// 银行卡号
var cardNumber = "23237532458972345234";
// 保留后4位
var lastFourNumber = cardNumber.slice(-4);
// 显示最后4位,前面显示*
var finalCard = lastFourNumber.padStart(17, "*")
console.log(finalCard)
日期填充案例
var date = new Date();
var year = date.getFullYear();
// 不满2位补充0
var month = (date.getMonth() + 1).toString().padStart(2, '0');
// 不满2位补充0
var day = (date.getDate()).toString().padStart(2, '0')
console.log(year, month, day)
Object Descriptors
Object.getOwnPropertyDescriptors()用来获取一个对象的所有自身属性的描述符。
const obj = {
name: "zhangsan",
age: 18
}
Object.defineDescriptors(obj)
ES10
flat
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
const nums = [10, 20, [2, 9], [[30, 40], [10, 45]], 78, [55, 88]]
const flatArr = (arr) => {
return arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) ? flatArr(cur) : cur)
}, [])
}
// ES10之前
console.log(flatArr(nums))
// flat
console.log(nums.flat(Infinity))
flatMap
首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
flatMap是先进行map操作,再做flat的操作;
flatMap中的flat相当于深度为1;
const message = ["Hello World", "你好啊 李银河", "my name is why"];
const msg1 = message.map(item => {
return item.split(' ')
})
// [["Hello", "world"], ["你好啊", "李银河"], ["my", "name", "is", "why"]]
const msg2 = message.flatMap(item => {
//先map 然后flat 深度为1
return item.split(' ')
})
console.log(msg1, msg2)
fromEntries
const obj = {
a: 1,
b: 2,
c: 3
}
// 对象转成entries ---> 遍历
const entries = Object.entries(obj)
entries.forEach(entry => {
console.log(entry[0], entry[1])
})
// 对象转成entries ---> 遍历
for (const [key, value] of entries) {
console.log(key, value)
}
// entries转换成对象 ES10新增
// Object.fromEntries的应用场景
const str = "name=why&age=18&height=1.88"
const params = new URLSearchParams(str)
// 将entries转换为 对象
const paramObj = Object.fromEntries(params)
console.log(paramObj)
trimStart tirmEnd
const message = " Hello World ";
console.log(message.trim());
console.log(message.trimStart());
console.log(message.trimEnd());
ES11
BigInt
在ES11之前大于MAX_SAFE_INTEGER的数值,表示的可能是不正确的。
const maxInt = Number.MAX_SAFE_INTEGER
console.log(maxInt + 1) // 可能表示出来不正确
// ES11之后
const bigInt = 99999999999999999999n;
console.log(bigInt + 10n); //可以正确表示
console.log(typeof bigInt); //bigInt
const num = 10;
// int 和 bigInt运算需要转换
console.log(bigInt + BigInt(num));
// bigInt 和 int运算需要转换
console.log(num / Number(10n));
??空值合并运算符
空值合并操作符,可以在使用可选链时设置一个默认值
const obj = {
friend: {
girlFriend: {
// name: "luck"
}
}
}
// 没有可选链操作符之前
if (obj.friend && obj.friend.girlFriend && obj.friend.girlFriend.name) {
console.log(obj.friend.girlFriend.name)
} else {
console.log(undefined)
}
// 可选链操作符 空值合并运算符
console.log(obj?.friend?.girlFriend?.name ?? "默认值")
// 或者
const foo = undefined; //当值是0 或者 是"" bar1有bug
const bar1 = foo || "default value";
const bar2 = foo ?? "defualt value";
?.可选链操作符
允许读取位于连接对象链深处的属性值,而不必明确验证链中的每个引用是否有效。
const obj = {
friend: {
girlFriend: {
name: "luck"
}
}
}
// 没有可选链操作符之前
if( obj.friend && obj.friend.girlFriend && obj.friend.girlFriend.name ) {
console.log(obj.friend.girlFriend.name)
} else {
console.log(undefined);
}
// 可选链操作符
console.log(obj?.friend?.girlFriend?.name);
Global This
JavaScript环境的全局对象,不同的环境获取的方式是不一样的。
比如在浏览器中可以通过this、window来获取
比如在Node中我们需要通过global来获取
ES11中对获取全局对象进行了统一的规范:globalThis
console.log(globalThis);
console.log(this); //浏览器上
console.log(global); //node中
for…in标准化
在ES11之前,虽然很多浏览器支持for…in来遍历对象类型,但是并没有被ECMA标准化。
在ES11中,对其进行了标准化,for…in是用于遍历对象的key的:
// for...in 标准化: ECMA
const obj = {
name: "why",
age: 18
}
for (const item in obj) {
console.log(item)
}
ES12
FinalizationRegistry
FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调 用一个清理回调。(清理回调有时被称为 finalizer );
可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值;
// ES12: FinalizationRegistry类
const finalRegistry = new FinalizationRegistry((value) => {
console.log("注册在finalRegistry的对象, 某一个被销毁", value)
})
function newArr() {
return new Array(1024 * 1024).fill(1)
}
//分配20个引用 让其引用保存在数组 不被释放
var arrList = [];
for (var i = 0; i < 20; i++) {
setTimeout(function () {
arrList.push(newArr())
}, i * 100)
}
setTimeout(() => {
for (var i = 0; i < 10; i++) {
//以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值
finalRegistry.register(arrList[i], i)
}
}, 4000)
// 释放10个引用 然后等待GC回收10个引用
// 这里要注意, 释放10个
setTimeout(function () {
for (var i = 0; i < 10; i++) {
arrList.shift()
}
}, 5000)
WeakRefs
如果我们默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用。如果我们希望是一个弱引用的话,可以使用WeakRef。
// ES12: WeakRef类
// WeakRef.prototype.deref:
// > 如果原对象没有销毁, 那么可以获取到原对象
// > 如果原对象已经销毁, 那么获取到的是undefined
let obj = { name: "why" };
let info = new WeakRef(obj);
// 置空 info是弱引用 会被回收的
obj = null
// 被回收之前 能够打印
console.log(info.deref()?.name)
// 等待GC回收obj 我们再来打印弱引用 info.deref()不存在的话会返回 undefined
// 这里假设是10秒回收,但是具体多久回收 我也不知道
setTimeout(() => {
// 用可选链 + 空值合并
console.log(info.deref()?.name ?? "啥也没有")
// 如果不用可选链
console.log(info.deref() && info.deref().name)
}, 10000)
logical assignment operators
// 1.逻辑或运算符
let message = ""
message = message || "Hello World";
message ||= "Hello World";
// 2.逻辑与操作符
let obj = {
name: "why",
foo() {
console.log('123')
}
}
obj0 = obj && obj.foo;
obj0()
obj &&= obj.name;
// 逻辑空运算符
let foo = null;
foo ??= "默认值"
console.log(foo)