
目录
十、可选链 ?. 可选链操作符 - JavaScript | MDN
执行上下文栈:Execution Context Stack,用于执行上下文的栈结构
执行上下文:Execution Context,代码在执行之前会先创建对应的执行上下文
变量对象:Variable Object,上下文关联的VO对象,用于记录函数和变量声明
全局对象:Global Object,全局执行上下文关联的VO对象
激活对象:Activation Object,函数执行上下文关联的VO对象
作用域链:scope chain,作用域链,用于关联指向上下文的变量查找
词法环境是一种规范类型,用于在词法嵌套结构中定义关联的变量、函数等标识符


执行上下文其实会关联两个词法环境 : LexicalEnvironment 和 VariableEnvironment
创建执行上下文时,其LexicalEnvironment和VariableEnvironment组件最初具有相同的值,他们的结构完全一样
LexicalEnvironment : 用于处理 let、const 和 function 声明的标识符

词法环境一旦被创建,let、const声明的变量也会同时被创建出来,但是从该作用域开始的位置一直到该变量被赋值的这块区域,都不能访问这个变量,否则就会报错,这个区域也被称之为 => 暂时性死区
VariableEnvironment : 用于处理 var 声明的的标识符

变量环境一旦被创建,变量也会同时被创建出来,同时会赋值为undefined,直到被赋值

在这个规范中有两种主要的环境记录值 : 声明式环境记录 和 对象环境记录


let : 从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量
const :
- // let
- let message = "你好"
- message2 = "世界"
- console.log(message2)
-
- // -----------------------------------------------------
-
- // const
- const str = "start"
- str = "coder" // 报错
-
- // const赋值引用类型
- const info = {
- name: "star",
- age: 18
- }
- // info = {} // 报错
- info.name = "kobe" // 可修改
词法环境一旦被创建,let、const声明的变量也会同时被创建出来,但是从该作用域开始的位置一直到该变量被赋值的这块区域,都不能访问这个变量,否则就会报错,这个区域也被称之为 => 暂时性死区
变量会被创建在包含他们的词法环境被实例化时,会提前创建,但是是不可以访问它们的,直到词法绑定被求值

ES6中新增了块级作用域,并且通过let、const、function、class声明的标识符是具备块级作用域的限制的
- 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那样的提升
- 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();

- // var变量可以重复声明
- var message = "Hello World"
- var message = "你好, 世界"
-
-
- // let/const不允许变量的重复声明
- let address = "广州市"
- // let address = "上海市" // 报错
- const info = {}
- // const info = {} // 报错
- // 1.var声明的变量会进行作用域的提升
- console.log(message) // 可以使用,值为undefined
- var message = "Hello World"
-
- // 2.let/const声明的变量: 没有作用域提升
-
- console.log(address) // 在赋值前访问,报错
- let address = "广州市"
- // 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
- 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在设计之初的一种语言缺陷
- 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
- 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是ES6中新增的一个基本数据类型,翻译为符号
- // 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"
- })
注 : 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
description : 可以在创建Symbol的同时,写入一个描述
- // 写入description
- const s3 = Symbol("ccc")
- // 获取description
- console.log(s3.description)
-
- // 就算写入相同的description,新生成的Symbol也是独一无二的
- const s4 = Symbol(s3.description)
- console.log(s3 === s4) // false
如果非要生成相同的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
在ES6之前,存储数据的结构主要有两种:数组、对象
在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复
Set有一个非常常用的功能就是给数组去重
Set常见的属性: size:返回Set中元素的个数;
Set常用的方法:
- // 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
- }
- 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']
- const names = ['abc', 'cba', 'nba', 'cba', 'nba'];
- // 先转成set,然后再解构成数组 或者 Array.from(new Set(names))
- const newNames = [...new Set(names)];
- console.log(newNames); // ['abc', 'cba', 'nba']
Set类似的另外一个数据结构称之为 WeakSet ,也是内部元素不能重复的数据结构
WeakSet常见的方法:
注意 :

- // 这里使用弱引用,这样当实例对象置为空的时候,数据会自动被回收
- // 如果使用了强引用,那么这里还在引用着实例对象,那么实例对象就不会被回收
- 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常见的属性: size:返回Map中元素的个数;
Map常见的方法:
- 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}
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的
和Map的区别 :
WeakMap常见的方法有四个:
注意 :
- const obj = {
- name: "star",
- friend: {
- name: "coder",
- running: function() {
- console.log("running~")
- }
- }
- }
-
- // 直接调用: 非常危险
- // 万一没有这个方法呢,就直接报错
- obj.friend.running()
- const obj = {
- name: 'star',
- friend: {
- name: 'coder',
- running: function () {
- console.log('running~');
- }
- }
- };
-
- // if判断: 麻烦/不够简洁
- if (obj.friend && obj.friend.running) {
- obj.friend.running();
- }
- 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);
- // 逻辑赋值运算符
- function foo(message) {
- // || 逻辑赋值运算符
- message = message || "默认值"
- // 转变
- message ||= "默认值"
-
- console.log(message)
- }
- // 逻辑赋值运算符
- function foo(message) {
- // ?? 逻辑赋值运算符
- message = message ?? "默认值"
- // 转变
- message ??= "默认值"
-
- console.log(message)
- }
- // &&逻辑赋值运算符
- 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
- const arr = ['abc', 'cab', 'ddd'];
- // 取到最后一位
- console.log(arr[arr.length - 1]); // ddd
- console.log(arr.at(-1)); // ddd
- // 取到倒数第二位
- console.log(arr.at(-2)); // cab
- const str = 'hello world';
- // 取到最后一位
- console.log(str[str.length - 1]); // d
- console.log(str.at(-1)); // d
- // 取到倒数第二位
- console.log(str.at(-2)); // l
FinalizationRegistry :
- 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
- let info = { name: "why", age: 18 }
- // 建立弱引用
- let obj = new WeakRef(info)
- 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)
- */