目录
十、可选链 ?. 可选链操作符 - JavaScript | MDN
一、ECMA新描述概念
ES6之前概念
-
执行上下文栈:Execution Context Stack,用于执行上下文的栈结构
-
执行上下文:Execution Context,代码在执行之前会先创建对应的执行上下文
-
变量对象:Variable Object,上下文关联的VO对象,用于记录函数和变量声明
-
全局对象:Global Object,全局执行上下文关联的VO对象
-
激活对象:Activation Object,函数执行上下文关联的VO对象
-
作用域链:scope chain,作用域链,用于关联指向上下文的变量查找
ES6开始
- 基本思路是相同的,只是对于一些词汇的描述发生了改变
- 执行上下文站和执行上下文也是相同的
1. 词法环境
词法环境是一种规范类型,用于在词法嵌套结构中定义关联的变量、函数等标识符
- 一个词法环境是由环境记录(Environment Record)和一个外部词法环境(oute;r Lexical Environment)组成
- Environment Records(环境记录):这个就是变量登记的地方了
- outer:outer 是个指向,包含本词法环境的外部词法环境,是作用域链能够链起来的关键,指向外部Lexical Environments(词法环境)的引用
- 一个词法环境经常用于关联一个函数声明、代码块语句、try-catch语句、evel语句,当它们的代码被执行时,词法环境被创建出来
执行上下文其实会关联两个词法环境 : LexicalEnvironment 和 VariableEnvironment
创建执行上下文时,其LexicalEnvironment和VariableEnvironment组件最初具有相同的值,他们的结构完全一样
LexicalEnvironment
LexicalEnvironment : 用于处理 let、const 和 function 声明的标识符
词法环境一旦被创建,let、const声明的变量也会同时被创建出来,但是从该作用域开始的位置一直到该变量被赋值的这块区域,都不能访问这个变量,否则就会报错,这个区域也被称之为 => 暂时性死区
VariableEnvironment
VariableEnvironment : 用于处理 var 声明的的标识符
变量环境一旦被创建,变量也会同时被创建出来,同时会赋值为undefined,直到被赋值
2. 环境记录
在这个规范中有两种主要的环境记录值 : 声明式环境记录 和 对象环境记录
- 声明式环境记录:声明性环境记录用于定义ECMAScript语言语法元素的效果,如函数声明、变量声明和直接将标识符绑定与ECMAScript语言值关联起来的Catch子句 let age = 19
- 对象式环境记录:对象环境记录用于定义ECMAScript元素的效果,例如WithStatement,它将标识符绑定与某些对象的属性关联起来 with(obj){}
二、let 和 const
let : 从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量
const :
- const关键字是constant的单词的缩写,表示常量、衡量的意思
- 它表示保存的数据一旦被赋值,就不能被修改
- 但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容
1. 基本使用
// let
let message = "你好"
message2 = "世界"
console.log(message2)
// -----------------------------------------------------
// const
const str = "start"
str = "coder" // 报错
// const赋值引用类型
const info = {
name: "star",
age: 18
}
// info = {} // 报错
info.name = "kobe" // 可修改
2. 暂时性死区
词法环境一旦被创建,let、const声明的变量也会同时被创建出来,但是从该作用域开始的位置一直到该变量被赋值的这块区域,都不能访问这个变量,否则就会报错,这个区域也被称之为 => 暂时性死区
变量会被创建在包含他们的词法环境被实例化时,会提前创建,但是是不可以访问它们的,直到词法绑定被求值
3. 块级作用域
ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的
01 - 变量
console.log(message); // undefined
console.log(age); // 报错,没有定义
{
var message = 'Hello World';
let age = 18;
const height = 1.88;
}
console.log(message); // Hello World
console.log(age); // 报错,没有定义
02 - 函数
函数不一样,有作用域提升,但不是像var那样的提升
foo(); // 这里是报错的
{
let age = 18;
function foo() {
console.log('foo function');
}
}
// 这里是可以访问的
foo();
// --------------------------------------
bar(); // 这里是报错的
if (1) {
function bar() {
console.log('foo function');
}
}
// 这里是可以访问的
bar();
// --------------------------------------
baz(); // 这里是报错的
let str = 1;
switch (str) {
case 1:
function baz() {
console.log('foo function');
}
break;
}
// 这里是可以访问的
baz();
三、let && const 和 var 的区别
1. 重复声明
- let、const不允许重复声明
- var 可以
// var变量可以重复声明
var message = "Hello World"
var message = "你好, 世界"
// let/const不允许变量的重复声明
let address = "广州市"
// let address = "上海市" // 报错
const info = {}
// const info = {} // 报错
2. 作用域提升
- let、const没有作用域提升,但是会在解析阶段被创建出来
- var 有
// 1.var声明的变量会进行作用域的提升
console.log(message) // 可以使用,值为undefined
var message = "Hello World"
// 2.let/const声明的变量: 没有作用域提升
console.log(address) // 在赋值前访问,报错
let address = "广州市"
3. 挂载到window上
- let、const不会挂载上去
- var 会
// 1.var定义的变量是会默认添加到window上的
var a = "Hello World"
var b = "广州市"
console.log(window.a) // Hello World
console.log(window.b) // 广州市
// 2.let/const定义的变量不会添加到window上的
let c = "Hello World"
let d = "广州市"
console.log(window.c) // undefined
console.log(window.d) // undefined
4. 块级作用域
- let、const 有
- var 没有
console.log(message); // undefined
console.log(age); // 报错,没有定义
{
var message = 'Hello World';
let age = 18;
const height = 1.88;
}
console.log(message); // Hello World
console.log(age); // 报错,没有定义
var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题,其实是JavaScript在设计之初的一种语言缺陷
四、模板字符串 ` `
- 使用 `` 符号来编写字符串,称之为模板字符串
- 在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容
1. 基本使用
const name = 'star';
const age = 18;
// 1. 进行数据拼接
const info = `my name is ${name}, age is ${age}`;
console.log(info); // my name is star, age is 18
// 2. 简单表达式运算
const bool = `我是成年人吗?${age > 19 ? '是' : '否'}`;
console.log(bool); // 我是成年人吗?否
// 3. 调用函数
function foo() {
return 'abc';
}
console.log(`my function is ${foo()}`); // my function is abc
2. 标签模版字符串
const name = 'star';
const age = 18;
// 标签模板字符串的用法
function foo(...args) {
/**
* args
* 第一个参数 : 被${}截取出来的字符串的数组
* 后面的参数 : ${}中传递过来的变量值
*/
console.log('参数:', args); // [ ['my name is ', ', age is ', ', height is ', ''] , "star", '18', '1.88' ]
}
// 使用 : 在函数名后面跟上模版字符串
foo`my name is ${name}, age is ${age}, height is ${1.88}`;
五、Symbol
Symbol是ES6中新增的一个基本数据类型,翻译为符号
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突
- Symbol可以用来生成一个独一无二的值,通过Symbol函数来生成的,生成后可以作为属性名
- Symbol即使多次创建,值也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的
1. 基本用法
// ES6之后可以使用Symbol生成一个独一无二的值
const s1 = Symbol();
// ------加入对象中,作为对象的一个key------
// 加入方式一
const obj = {
[s1]: 'aaa'
};
// 加入方式二
const s2 = Symbol();
obj[s2] = 'bbb';
// 加入方式三
const s3 = Symbol();
Object.defineProperty(obj, s3, {
value: "ccc"
})
2. 获取symbol对应的key
注 : Object.keys( ) 获取不到 symbol 作为key的
const s1 = Symbol(); // aaa
const s2 = Symbol(); // bbb
const obj = {
name: 'why',
age: 18,
[s1]: 'aaa',
[s2]: 'bbb'
};
// 只能获取到普通的作为字符串的key
console.log(Object.keys(obj)); // ['name', 'age']
// 可以获取到Symbol的key
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol()]
const symbolKeys = Object.getOwnPropertySymbols(obj);
// 1. 通过便利,通过Symbol的key获取值
for (const key of symbolKeys) {
console.log(obj[key]); // aaa bbb
}
// 2. 直接获取值
console.log(obj[s1]); // aaa
console.log(obj[s2]); // bbb
3. description
description : 可以在创建Symbol的同时,写入一个描述
// 写入description
const s3 = Symbol("ccc")
// 获取description
console.log(s3.description)
// 就算写入相同的description,新生成的Symbol也是独一无二的
const s4 = Symbol(s3.description)
console.log(s3 === s4) // false
4. Symbol.for
如果非要生成相同的Symbol : 如果相同的key, 通过 Symbol.for 可以生成相同的Symbol值
// 这样创建,才可以
const s5 = Symbol.for("ddd")
const s6 = Symbol.for("ddd")
console.log(s5 === s6) // true
// 获取传入的key
console.log(Symbol.keyFor(s5)) // ddd
六、Set 数据结构
在ES6之前,存储数据的结构主要有两种:数组、对象
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复
Set有一个非常常用的功能就是给数组去重
Set常见的属性: size:返回Set中元素的个数;
Set常用的方法:
- add(value):添加某个元素,返回Set对象本身;
- delete(value):从set中删除和这个值相等的元素,返回boolean类型;
- has(value):判断set中是否存在某个元素,返回boolean类型;
- clear():清空set中所有的元素,没有返回值;
- forEach(callback, [, thisArg]):通过forEach遍历set
- Set是支持for of的遍历的
1. 基本使用
// 1. 创建Set
const set = new Set();
console.log(set);
// 2. 添加元素
set.add(10);
set.add(22);
set.add(35);
// 相同元素,添加失败
set.add(22);
console.log(set); // {10, 22, 35}
// 3. 删除元素
set.delete(10);
console.log(set); // {22, 35}
// 4. 是否拥有
console.log(set.has(10), set.has(35)); // false true
// 5. 遍历set
set.forEach((item) => console.log(item)); // 22 35
// 6. set支持for...of
for (const item of set) {
console.log(item); // 22 35
}
2. 数组去重
01 - 循环
const names = ['abc', 'cba', 'nba', 'cba', 'nba'];
const newNames = [];
for (const item of names) {
if (!newNames.includes(item)) {
newNames.push(item);
}
}
console.log(newNames); // ['abc', 'cba', 'nba']
02 - 使用Set去重
const names = ['abc', 'cba', 'nba', 'cba', 'nba'];
// 先转成set,然后再解构成数组 或者 Array.from(new Set(names))
const newNames = [...new Set(names)];
console.log(newNames); // ['abc', 'cba', 'nba']
七、weakSet 数据结构
Set类似的另外一个数据结构称之为 WeakSet ,也是内部元素不能重复的数据结构
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型
- 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收
WeakSet常见的方法:
- add(value):添加某个元素,返回WeakSet对象本身
- delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型
- has(value):判断WeakSet中是否存在某个元素,返回boolean类型
注意 :
- 因为WeakSet只是对对象的弱引用,如果遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
- 所以存储到WeakSet中的对象是没办法获取的
- 也就是,只能存储进去,不能拿出来用
1. 解析
2. 应用场景
// 这里使用弱引用,这样当实例对象置为空的时候,数据会自动被回收
// 如果使用了强引用,那么这里还在引用着实例对象,那么实例对象就不会被回收
const pWeakSet = new WeakSet();
class Person {
constructor() {
// 每次创建对象时,把对象存储到WeakSet中
pWeakSet.add(this);
}
running() {
// 判断调用该方法的是不是实例对象
if (!pWeakSet.has(this)) {
console.log('Type error: 调用的方式不对');
return;
}
console.log('running~');
}
}
let p = new Person();
p.running(); // 可正常调用 running~
const runFn = p.running;
runFn(); // 报错 Type error: 调用的方式不对
const obj = { run: runFn };
obj.run(); // 报错 Type error: 调用的方式不对
八、Map 数据结构
新增的数据结构是Map,用于存储映射关系
- 对象存储映射关系只能用字符串 || Symbol 作为属性名(key)
- 希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key
Map常见的属性: size:返回Map中元素的个数;
Map常见的方法:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
- clear():清空所有的元素;
- forEach(callback, [, thisArg]):通过forEach遍历Map
- Map是支持for of的遍历的
基本使用
const info = { name: 'star', age: 18 };
const obj = { id: 999 };
// 1. 创建map对象
const map = new Map();
// 2. 增加元素
map.set(info, 'aaaa');
map.set(obj, 'bbbbb');
console.log(map); // Map(2) {{…} => 'aaaa', {…} => 'bbbbb'}
// 3. 更改内容
map.set(info, 'cccc');
console.log(map); // Map(2) {{…} => 'cccc', {…} => 'bbbbb'}
// 4. 获取内容
console.log(map.get(info)); // cccc
// 5. 删除内容
map.delete(info);
console.log(map); // Map(1) {{…} => 'bbbbb'}
// 6. 判断是否存在
console.log(map.has(info), map.has(obj)); // false true
// 7. forEach方法
map.forEach((item) => console.log(item)); // bbbbb
// 8. for...of遍历
for (const item of map) {
const [key, value] = item;
console.log(key, value); // {id: 999} 'bbbbb'
}
// 9. 清空内容
map.clear();
console.log(map); // Map(0) {size: 0}
九、weakMap 数据结构
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的
和Map的区别 :
- 区别一: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类型
注意 :
- WeakMap也是不能遍历的
- 没有forEach方法,也不支持通过for of的方式进行遍历
十、可选链 ?. 可选链操作符 - JavaScript | MDN
1. 直接调用 : 很危险
const obj = {
name: "star",
friend: {
name: "coder",
running: function() {
console.log("running~")
}
}
}
// 直接调用: 非常危险
// 万一没有这个方法呢,就直接报错
obj.friend.running()
2. 判断调用 : 很麻烦
const obj = {
name: 'star',
friend: {
name: 'coder',
running: function () {
console.log('running~');
}
}
};
// if判断: 麻烦/不够简洁
if (obj.friend && obj.friend.running) {
obj.friend.running();
}
3. 链式调用 : 很可以
const obj = {
name: 'star',
friend: {
name: 'coder',
running: function () {
console.log('running~');
}
}
};
// 3.可选链的用法: ?.
/**
* obj?. => 是否有obj对象
* obj?.friend?. => obj对象中是否有friend属性
* obj?.friend?.running?. => obj的friend属性中是否有running这个方法
*/
obj?.friend?.running?.();
十一、空值合并运算符 ??
let info = undefined; // 默认值
info = null; // 默认值
info = ''; // ''
info = 0; // 0
info = false; // false
// ??: 空值合并运算符 当是null || undefined时,使用后面的
info = info ?? '默认值';
console.log(info);
十二、逻辑赋值运算符
1 - | | 逻辑赋值运算符
// 逻辑赋值运算符
function foo(message) {
// || 逻辑赋值运算符
message = message || "默认值"
// 转变
message ||= "默认值"
console.log(message)
}
2 - ?? 逻辑赋值运算符
// 逻辑赋值运算符
function foo(message) {
// ?? 逻辑赋值运算符
message = message ?? "默认值"
// 转变
message ??= "默认值"
console.log(message)
}
3 - && 逻辑赋值运算符 不常用~
// &&逻辑赋值运算符
let obj = {
name: 'star',
running: function () {
console.log('running~');
}
};
obj && obj.running && obj.running();
let name1 = obj && obj.name;
console.log(name); // star
// 强行使用
obj = obj && obj.name;
console.log(obj); // star
// 转变
obj &&= obj.name;
console.log(obj); // star
十三、.at ( )
1. 数组
const arr = ['abc', 'cab', 'ddd'];
// 取到最后一位
console.log(arr[arr.length - 1]); // ddd
console.log(arr.at(-1)); // ddd
// 取到倒数第二位
console.log(arr.at(-2)); // cab
2. 字符串
const str = 'hello world';
// 取到最后一位
console.log(str[str.length - 1]); // d
console.log(str.at(-1)); // d
// 取到倒数第二位
console.log(str.at(-2)); // l
十四、FinalizationRegistry监听GC
FinalizationRegistry :
- FinalizationRegistry 对象可以让你在对象被垃圾回收时请求一个回调
- 当注册的对象被回收的时候,会促发回调函数
- 可以通过调用register方法,注册任何你想要清理回调的对象,传入该对象和所含的值
let obj = { name: 'why', age: 18 };
let info = { name: 'kobe', age: 30 };
// 2. 如果被回收了,会促发这个回调函数
const finalRegistry = new FinalizationRegistry((value) => {
console.log('某一个对象被回收了:', value);
});
// 1. 监听注册的对象是否被回收
finalRegistry.register(obj, 'why');
finalRegistry.register(info, 'kobe');
// 3. 赋值为null后,不会马上回收,有惰性的GC
// obj = null
info = null;
十五、WeakRef 弱引用
如果默认将一个对象赋值给另外一个引用,那么这个引用是一个强引用:
如果希望是一个弱引用的话,可以使用WeakRef
1. 建立弱引用 : WeakRef
let info = { name: "why", age: 18 }
// 建立弱引用
let obj = new WeakRef(info)
2. 还原原始值 : deref
let info = { name: 'why', age: 18 };
// 建立弱引用
let obj = new WeakRef(info);
// 从弱引用中拿到值 这样拿值!!
console.log(obj.deref().name, obj.deref().age);
/**
* 如果这样拿值的话
* const infoRef = obj.deref()
* 相当于这里又是强引用了,那么使用WeakRef就变得无意义了
* console.log(infoRef)
*/