重学JavaScript(基础)
let、const、var
let、const、var 的区别
1.是否存在变量提升?
var声明的变量存在变量提升(将变量提升到当前作用域的顶部)。即变量可以在声明之前调用,值为undefinedlet和const不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报ReferenceError错
2.是否存在暂时性死区?let和const存在暂时性死区。即只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响
3.是否允许重复声明变量?
var允许重复声明变量。let和const在同一作用域不允许重复声明变量。
4.是否存在块级作用域?
- var 不存在块级作用域。
- let 和 const 存在块级作用域
- 块作用域由
{ }包括,if语句和for语句里面的{ }也属于块作用域
- 块作用域由
5. 是否能修改声明的变量?
var和let可以。const声明一个只读的常量。一旦声明,常量的值就不能改变。const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
什么是暂时性死区(TDZ)
在代码块内,使用
let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) { |
解构赋值
数组解构
const foo = ["one", "two", "three"];
const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"对象解构
const obj = {
a: "1",
b: "2",
}
const { a, b } = obj
console.log(a, b) // 1 2默认参数
const obj = {
a: "1",
}
const { a, b = 'b' } = obj
console.log(a, b) // 1 b剩余参数
...const foo = ["one", "two", "three"];
const [a, ...b] = foo;
console.log(a); // "one"
console.log(b); // [ 'two', 'three' ]赋值给新的变量名
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;
console.log(foo); // 42
console.log(bar); // true
数组遍历
forforEachmap- 返回新数组,不会改变原数组
filter- 过滤some- 返回布尔值,有一个符合条件就返回 true
every- 返回布尔值,每一个都符合条件才返回 true
reducefor...of、for...in- [[for…in和for…of]]
find- findIndex
- values ()
- keys ()
- entries ()
const arr = [1, 2, 3] |
数组的扩展
Array.from ()
从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。const set = new Set(['foo', 'bar', 'baz', 'foo']);
Array.from(set);
// [ "foo", "bar", "baz" ]Ï
Array.of()
创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。const arr = Array.of(1, true, "123", [1, 2, 3], { age: 18 })
console.log("🚀 ~ ", arr) // [ 1, true, '123', [ 1, 2, 3 ], { age: 18 } ]
fill()
用一个固定值填充一个数组中从起始索引(默认为 0)到终止索引(默认为 array.length)内的全部元素。它返回修改后的数组。[1, 2, 3].fill(4); // [4, 4, 4]
[1, 2, 3].fill(4, 1); // [1, 4, 4]
[1, 2, 3].fill(4, 1, 2); // [1, 4, 3]
[1, 2, 3].fill(4, 1, 1); // [1, 2, 3]
[1, 2, 3].fill(4, 3, 3); // [1, 2, 3]
[1, 2, 3].fill(4, -3, -2); // [4, 2, 3]
[1, 2, 3].fill(4, NaN, NaN); // [1, 2, 3]
[1, 2, 3].fill(4, 3, 5); // [1, 2, 3]
Array(3).fill(4); // [4, 4, 4]
[].fill.call({ length: 3 }, 4); // {0: 4, 1: 4, 2: 4, length: 3}
// Objects by reference.
var arr = Array(3).fill({}) // [{}, {}, {}];
arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]
includes()
用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
[1, 2, NaN].indexOf(NaN) // -1
函数的参数
参数的默认值
const fn = (a, b) => { |
const fn = (a, b = 2) => { |
const fn = (a = 1, b) => { |
注意:默认参数应该写在后面,必填参数放在前面
解构赋值
[[#解构赋值]]
const fn = (name, { age = 18, work = "front" } = {}) => { |
length 属性
const fn = (a, b, c) => {} |
可以使用 length 获取对象必传参数的个数
函数参数作用域
const x = 13; |
在这个例子中,我们定义了一个函数 fn,它接受两个参数 x 和 y,其中 y 的默认值为 x。当我们调用 fn(1) 时,函数内部的 x 被赋值为1,而由于没有传入 y 的值,所以 y 的默认值为函数内部的 x,因此 y 的值也为1,所以最终打印出来的是1
const x = 13; |
在这个例子中,我们定义了一个函数 fn2,它只接受一个参数 y,且 y 的默认值为外部定义的 x。当我们调用 fn2() 时,由于没有传入任何参数,所以 y 的默认值为外部的 x,即13,因此最终打印出来的是13。
function fn3(y = x) { |
在这个例子中,我们定义了一个函数fn3,它只接受一个参数y,且y的默认值为外部定义的x。但是,在函数内部,我们又定义了一个局部变量x并赋值为2。当我们调用fn3()时,JavaScript会在函数体内查找默认参数y的值,但此时x还未定义,因此会报错x is not defined。
这些例子展示了JavaScript函数参数作用域的一些特点:
- 函数内部的参数会覆盖外部定义的同名变量。
- 默认参数的作用域是在函数体内部。
- 函数内部定义的局部变量会遮蔽外部同名变量,但默认参数的作用域不受影响。
扩展运算符、剩余参数、arguments
详情见文档:
箭头函数
() => {}
箭头函数与普通函数区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用 new
- 箭头函数内没有
arguments,可以用扩展运算符...解决 - 箭头函数没有
this, 箭头函数的this,始终指向父级上下文(箭头函数的this取决于定义位置父级的上下文,跟使用位置没关系,普通函数this指向调用的那个对象) - 箭头函数不能通过
call() 、 apply() 、bind()方法直接修改它的 this 指向。(call、apply、bind会默认忽略第一个参数,但是可以正常传参) - 箭头函数没有原型属性
对象的扩展
属性简写
const name = "zs" |
属性名表达式
const name = "zs" |
Object.is ()
Object.is()静态方法确定两个值是否为相同值。
// 案例 1:评估结果和使用 === 相同 |
Object.is() 与 == 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 “” == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。
Object.is() 也不等价于 === 运算符。Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。=== 运算符(和 == 运算符)将数值 -0 和 +0 视为相等,但是会将 NaN 视为彼此不相等。
Object.assign()
Object.assign() 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
const obj = { a: 1 }; |
in
in :如果指定的属性在指定的对象或其原型链中,则 in 运算符**返回 true。// 数组
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees // 返回true
3 in trees // 返回true
6 in trees // 返回false
"bay" in trees // 返回false (必须使用索引号,而不是数组元素的值)
"length" in trees // 返回true (length是一个数组属性)
Symbol.iterator in trees // 返回true (数组可迭代,只在ES2015+上有效)
// 内置对象
"PI" in Math // 返回true
// 自定义对象
var mycar = {make: "Honda", model: "Accord", year: 1998};
"make" in mycar // 返回true
"model" in mycar // 返回true
遍历对象
for... inKeys ()Object.getOwnPropertyNames ()Reflect.ownKeys ()
深拷贝、浅拷贝
基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中


深拷贝和浅拷贝的区别
1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
浅拷贝
数组浅拷贝:
// 直接遍历 |
对象浅拷贝:
// 直接遍历 |
深拷贝
用深拷贝最后要递归到全部是基本值,不然可能会陷入死循环/循环引用,导致栈溢出
TODO(待理解)⭐: 处理过的数据使用 map 结构缓存起来 >> 递归的时候碰到相同的数据 >> 直接使用缓存里面的
**1. 先转换成字符串,在转换成(数组/对象)JSON.parse(JSON.stringify(XXXX))
[!tip] 注意
缺点:不能拷贝里面的函数
const array = [{ number: 1 }, { number: 2 }, { number: 3 }]; |
2、递归实现简单的深拷贝:
const cloneDeep = (target) => { |
3、解构赋值法实现一层拷贝
4、Object.assign() 实现一层深拷贝
5、 lodash第三方库
在引用类型的情况下,拷贝分为浅拷贝和深拷贝:
- 浅拷贝复制的是对象的引用,因此当属性为对象时,浅拷贝只复制一层,导致两个对象指向同一个地址。
- 深拷贝则是递归地复制对象的所有层次,包括属性为对象的情况,因此深拷贝会创建一个全新的对象栈,使得两个对象指向不同的地址。
