变量定义形式
:::color3 var 定义变量有什么问题?
- 变量提升
- 无法形成词法作用域
- 可以随意篡改变量值,重复声明
:::
let
从ES6 开始,Javascript 引入了let 和 const 关键字来定义变量,这是 ECMAScript 新增的两种声明变量的方式, 相较于 var 关键字具有更多的优势。
let底层实现过程
- 编译阶段
在代码编译阶段,编译器会扫描整个函数体(或全局作用域),查找所有使用let 定义的变量,为这些变量生成一个初始值为 undefined 的词法环境(LexicalEnvironment)并将其保存在作用域链中。
2.进入执行上下文
当进入执行块级作用域(包括for、if、while 和switch 等语句块)后,会创建一个新的词法环境。如果执行块级作用域中包含let 变量声明语句,这些变量将被添加到这个词法环境的环境记录中。
3.绑定变量值
运行到let 定义的变量时,Javascript引擎会在当前词法环境中搜索该变量。首先在当前环境记录中找到这个变量, 如果不存在变量,则向包含当前环境的外部环境记录搜索变量,直到全局作用域为止。如果变量值没有被绑定, Javascript引擎会将其绑定为 undefined,否则继续执行其他操作。
4.实现块级作用域
使用let 定必变量时,在运行时不会在当前作用域之外创建单独的执行上下文,而是会创建子遮蔽(shadowing)新环境。在子遮蔽的词法环境中,变量的值只在最接近的块级作用域内有效。
const
- const 关键字用于声明一个块级作用域的只读常量,一旦 const 声明了某个变量,就不能使用赋值语句改变它的值。
- 常量必须在声明时进行初始化
- 更加安全
- 使用let 和 const 可以有效地避免一些变量作用域混淆的问题。通过使用块级作用域,我们可以让变量只在指定代码块内部有效,避免了不必要的变量污染和冲突。
- 更加简洁
- 使用let 和 const可以大量減少代码量,并且更加易于维护。在使用var 时,由于变量作用域问题,经常需要添加额外的语句进行变量定义、检查和清除等操作,而使用let 和 const可以直接在代码中进行定义和使用,更加简洁和高效。
- 更加规范
- 使用 let 和 const可以使代码更加规范,让代码更好读懂、易于维护。随着 Javascript 的逐渐发展,代码规范性和可读性越来越重要,let 和 const 关键字的引入正是为了更好地实现这一目标。
- 综上所述,let 和 const 关键字可以使 Javascript 更加安全、简洁和规范,有效地解决了以往 JavaScript 变量定义存在的一些问题,带来更佳的开发体验。
cosnt底层实现过程
const 具有与let 相同的底层实现原理,区别在于 const 定义的变量被视为常量(在赋值之后无法更改),因此变量声明时必须初始化。此外,应该注意的是,使用const声明的对象是可以修改属性的。在定义 const 对象时,对象本身是常量,而不是对象的属性。只有对象本身不能被修改,而对象包含的属性可以任意修改。
Symbol
Symbol 是 ECMAScript 6 (ES6)中引入的一种新的基本数据类型。它是一种唯一且不可变的数据类型,用于对象属性的标识符。
基础使用
创建 Symbol
- Symbol 可以通过 Symbol () 函数创建。每个 Symbol 都是唯一的,即使它们的描述(description)相同。
用作对象属性
- Symbol 最常见的用途是用作对象属性的键。由于 Symbol是唯一的,因此可以避免属性名冲突
隐藏属性
- Symbol定义的属性不会出现在普通的对象遍历操作中(例如 for...in、object.keys()),但是可以使用 object.getOwnPropertysymbols()方法获取对象的Symbol 属性。
高级使用
全局 Symbol 注册表
通过 Symbol.for (key)和 Symbol.keyFor (symbol)方法,可以创建和共享全局 Symbol。
Symbol.for(key)会检查是否已经存在以该key创建的Symbol,如果存在则返回它,否则创建一个新的 Symbol 并注册到全局 Symbol注册表中。
内置 Symbol
Symbol.iterator:定义对象的默认迭代器。
Symbol.asyncIterator :定义对象的异步迭代器。
Symbol.hasInstance:自定义 instanceof 操作符的行为。
Symbol.isConcatSpreadable:定义数组在 Array.prototype.concat 中是否可展开。
Symbol.species:定义派生对象时使用的构造函数。
Symbol.toPrimitive :定义对象转换为原始值时的行为。
Symbol.toStringTag:自定义对象的 toString 方法返回的字符串标签。
Symbol.unscopables:定义对象在 with 语句中哪些属性不可用。
Set, Map, WeakSet, WeakMap
ECMAScript 6 (ES6) 引入了新的数据结构 Set、Map、WeakSet 和WeakMap。它们提供了比原始对象和数组更灵活和高效的存储方式。
Set
- Set 是一种类似于数组的数据结构,其所有成员都是唯一的。它的主要特点是
- 不允许有重复的值
- Set 对象可以存储任何类型的值,无论是原始值还是对象引用
Map
- Map 是一种键值对集合,其中键可以是任何类型的值。与普通对象不同,Map 的键可以是任何类型(包括对象、函数等)。
WeakSet
- Weakset 是一种类似于 set 的集合,但是它只能存储对象引用,且这些对象是弱引用。这意味着,如果没有其他对该对象的引用,垃圾回收器会自动回收该对象。
WeakMap
- WeakMap是一种键值对集合,其中键是对象引用,值可以是任意类型。WeakMap 中的键是弱引用,即如果没有其他对该键的引用,垃圾回收器会自动回收该键和对应的值。
:::info
- Set
- 存储唯一值的集合
- 允许任何类型的值
- Map
- 键值对集合
- 键可以是任何类型
- WeakSet
- 只能存储对象引用的集合
- 对象是弱引用,允许垃圾回收
- WeakMap
- 键值对集合
- 键必须是对象,键是弱引用,允许垃圾回收
:::
模板字符串
类名很容易重复冲突,解决这个问题的方法,react 可以使用emotion(开发项目)、styled-components、stitches (组件库可以选他,针对组件变体非常方便)
Javascript 模板字符串是 ES6 中新增的一种特殊的字符串语法,它允许嵌入表达式和变量,通过$仔拼接字符串和表达式,相比传统的字符串拼接来说,模板字符串更具可读性和可维护性。
底层实现原理
模板字符串的实现原理,可以大致分为两个步骤:首先,JavaScript 引擎会将模板字符串解析成一个函数调用表达式;接着,这个表达式会被执行,并输出一个最终的字符串。
对于第一步,当 Javascript 引擎解析模板字符串时,它会将特殊字符和变量值分割成多个参数,并将它们作为函数调用的参数传递给一个名为 Tagged Template 的函数。该函数的第一个参数是一个数组,其中包含原始模板字符串中的所有字符文字,除了所有插入字符。其余参数则是与模板字符串插值表达式相对应的插入值。
tagged template
- styled-components
- emotion(mui 使用的)
使用 tagged template
除了默认使用模板字符串外,我们还可以自定义 tagged template 来处理模板字符串。标记模板只需要定义一个函数,这个函数的第一个参数为一个标记数组,剩余其他参数是字符串中替换表达式的值,返回值是最终拼接好的字符串。这种模式在一些库中应用广泛,例如 styled-components。
解构语法
数组解构底层原理
对数组解构的底层实现分为两个过程:第一步是使用取值函数(getter)读取数组中对应位置的值,第二步是将取得的值赋值给目标变量。
对象解构底层原理
解构对象变量的过程与解构数组变量非常类似。将会遍历对象中的每一个属性,然后在解构表达式中查找同名的变量。
嵌套解构底层原理
嵌套解构是指解构表达式中还包含其他的数组或对象解构。嵌套解构的底层实现和单层解构类似,操作每个元素或属性时都需要依次使用取值函数(getter)进行操作。
默认值
解构语法还支持给目标变量提供默认值,在无法解构出对应的值时会使用默认值。默认值可以是任何JavaScript 表达式,包括函数调用、变量名、运算符等。
JavaScript 引擎对于解构语法实现的细节
在 JavaScript 引擎处理解构语法时,会执行以下步骤:
- 如果右边(要解构的对象)是一个具有 Iterator 接口的对象,则需要调用其 iterator 方法,为解构过程创建一个迭代器。迭代器可以让我们对要解构的对象进行遍历,并将其每个属性的取值传递给左边(解构的目标)中相应的变量。
- 对要解构的目标进行判断,如果为无法解构的值(如 undefined 或 null),则抛出 TypeError。
- 如果解构语法中指定了默认值,则在对象无法解构到值(undefined)时使用默认值。
- 嵌套解构会在目标中继续求值以解构嵌套的变量。
- 解构对于数组和字符串元素时,会按照索引顺序进行赋值;而对于对象中的元素时,会按照属性名进行赋值。
- 对于对象的解构,会从对象中取出相应属性的值,然后复制到与解构表达式中相应的变量中。如果解构表达式中指定的变量名与属性名不同,需要使用“key: value” 表示法进行定义。
- 解构表达式是完全可以包含剩余运算符的,这样即可匹配对象或数组中剩余的属性,将其赋值到相应的变量上。
- 如果解构表达式中不存在取值函数(getter) 和设置函数(setter),则此时解构赋值可以在性能上比原生赋值语句快一些。
箭头函数
箭头函数的原理是基于Javascript中的闭包、this和参数作用域。在箭头函数中,this关键字始终指向函数所在上下文的this指针,而不是所在作用域的this指针
:::info 箭头函数与普通函数有哪些不同?
-
this 指向,箭头函数不能定义构造器
-
不能 new
-
内部无 arguments 对象
-
this 绑定方法失效,比如:call apply bind
:::
箭头函数主要使用场景
- 当我们需要使用更简洁的语法来定义函数时,可以使用箭头函数代替传统的函数定义语法。箭头函数更为简洁、 易读,可以使用单行语法来代替多行语法。
- 当我们需要引用所在父级的this指针时,可以使用箭头函数,因为箭头函数中的this指向的是全局对象,而不是函数调用时的上下文对象。而不使用箭头函数时,这里会因为this指针指向的问题而带来一些不便。
- 在函数式编程中,箭头函数用于编写函数式代码时,简化函数的定义和函数调用的过程
不能使用箭头函数的场景
虽然箭头函数在许多应用场景中表现出极大的优势,但在某些情况下还是不能正常使用。常见的限制场景如下:
1.不能用作构造函数:箭头函数没有自己的this指针,所以不能作为构造函数来使用。因此,不能使用new关键字来调用箭头函数来创建一个新对象。
2.不能使用 arguments 关键字:在箭头函数中,函数的参数为指定的参数,没有额外的 arguments 对象。如果需要使用 arguments 参数,必须使用常规的函数语法。
3.不能通过 call()、apply()方法修改this指向:对于箭头函数,它的this 指针指向词法作用域中的this值,无法通过call()、apply()方法来修改。
Proxy
Proxy 是 ECMAScript 6(ES6) 中引入的一种强大的工具,用于定义对象的基本操作(如属性访问、赋值、枚举、函数调用等)的自定义行为。通过 Proxy 对象,我们可以拦截并自定义对象的许多操作,从而实现许多高级功能。
常用的捕获器(traps)
捕获器是代理对象在操作目标对象时触发的函数。常用的捕获器包括
- get(target, property, receiver):拦截读取属性操作
- set(target, property, value, receiver);拦截属性设置操作
- has(target, property):拦截操作符
- deleteProperty(target, property) : 拦截 delete 操作符
- apply(target, thisArg, argumentsList):拦截函数调用
应用场景
数据绑定与观察
- 通过 Proxy,可以实现对对象变化的监听,从而实现数据绑定或观察者模式
虚拟化和延迟计算
- 可以通过 Proxy 实现对象的虚拟化,延迟计算某些属性或方法的结果,直到需要时再进行计算
输入验证
- 可以使用 Proxy 拦截对象属性的设置,进行输入验证
Reflect
JavaScript 反射(reflection)是一种能够在运行时检查、修改对象、类和函数等程序结构的能力,通过反射,我们可以读取和修改对象属性、调用对象方法、定义新属性、修改原型等。
Javascript 通过 Reflect 对象提供了一组操作对象的 API,可以访问、检查和修改对象上的属性和方法。Reflect API 方法与对应对象的同名方法具有相同的功能,例如 Reflect.get()方法对应对象的 obji.get()方法,Reflect.set()方法对应对象 obj.set()方法等。
:::info 反射的优势:不关心传入需要变更对象的具体实现,只需要关注想要更改的属性
:::
:::info Reflect API 实现了对象的反射、代理等功能,它为我们提供了一些强大而便捷的工具,使得我们可以在运行时动态地查看、检查和修改对象的属性和行为。Reflect 反射在 Javascript 中的应用非常广泛,可以用于类似响应式编程、面向对象编程等各种场景。
:::
相关问题
对箭头函数的理解
特点和优点
- 箭头函数省去了 function 关键字,语法更加简洁
- 箭头函数不会创建自己的 this,它会捕获其所在上下文的this 值作为自己的this。这在回调函数中尤其有用,可以避免常见的 this 绑定问题。
- 箭头函数没有自己的 arguments 对象,但可以通过rest 参数语法获取参数
- 箭头函数不能使用 new 关键字,因为它们没有[[Construct]方法
使用场景
- 箭头函数特别适用于需要简短函数表达式的场景,例如数组的操作(如 map、filter、reduce 等)和回调函数。
相关资料
- Symbol 源码:chromium.googlesource.com/v8/v8.git/+…
- React中Symbol应用:github.com/facebook/re…
- React中WeakMap:github.com/facebook/re…
- Vue3中WeakMap:github.com/vuejs/core/…
- Tagged Template 在 styled-components 中的运用:github.com/styled-comp…
- vue vine:vue-vine.dev/
- Proxy:chromium.googlesource.com/v8/v8.git/+…
- Vue3源码中proxy:github.com/vuejs/core/…
- Reflect源码:chromium.googlesource.com/v8/v8.git/+…
- 弱引用处理:chromium.googlesource.com/v8/v8.git/+…