重学JavaScript(基础)

let、const、var

let、const、var 的区别

1.是否存在变量提升?

  • var声明的变量存在变量提升(将变量提升到当前作用域的顶部)。即变量可以在声明之前调用,值为undefined
  • letconst不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报ReferenceError

2.是否存在暂时性死区?
let和const存在暂时性死区。即只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响

3.是否允许重复声明变量?

  • var允许重复声明变量。
  • letconst在同一作用域不允许重复声明变量。

4.是否存在块级作用域?

  • var 不存在块级作用域。
  • let 和 const 存在块级作用域
    • 块作用域由{ }包括,if语句和for语句里面的{ }也属于块作用域

5. 是否能修改声明的变量?

  • varlet可以。
  • const声明一个只读的常量。一旦声明,常量的值就不能改变。const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

什么是暂时性死区(TDZ)

在代码块内,使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

if (true) {
  // TDZ开始
  a = 'abc'; // ReferenceError
  console.log(a); // ReferenceError

  let a; // TDZ结束
  console.log(a); // undefined

  a = 123;
  console.log(a); // 123
}

解构赋值

官方文档

  1. 数组解构
const foo = ["one", "two", "three"];

const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
  1. 对象解构
const obj = {
  a: "1",
  b: "2",
}

const { a, b } = obj
console.log(a, b) // 1 2
  1. 默认参数
const obj = {
  a: "1",
}

const { a, b = 'b' } = obj
console.log(a, b) // 1 b
  1. 剩余参数 ...
const foo = ["one", "two", "three"];

const [a, ...b] = foo;
console.log(a); // "one"
console.log(b); // [ 'two', 'three' ]
  1. 赋值给新的变量名
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;

console.log(foo); // 42
console.log(bar); // true

数组遍历

  1. for
  2. forEach
  3. map
    • 返回新数组,不会改变原数组
  4. filter - 过滤
  5. some
    • 返回布尔值,有一个符合条件就返回 true
  6. every
    • 返回布尔值,每一个都符合条件才返回 true
  7. reduce
  8. for...offor...in
    • [[for…in和for…of]]
  9. find
  10. findIndex
  11. values ()
  12. keys ()
  13. entries ()
const arr = [1, 2, 3]

for (const val of arr.values()) {
  console.log(val) // 1 2 3
}

for (const val of arr.keys()) {
  console.log(val) // 0 1 2
}

for (const [index, item] of arr.entries()) {
  console.log(index, item)
  // 0 1
  // 1 2
  // 2 3
}

数组的扩展

Array.from ()

Array.from()

从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

const set = new Set(['foo', 'bar', 'baz', 'foo']);
Array.from(set);
// [ "foo", "bar", "baz" ]Ï

Array.of()

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()

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()

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) => {
  console.log(a, b) // 1 2
}

fn(1, 2) 
const fn = (a, b = 2) => {
  console.log(a, b) // 1 2
}

fn(1)
const fn = (a = 1, b) => {
  console.log(a, b) // 10 undefined
}

fn(10)

注意:默认参数应该写在后面,必填参数放在前面

解构赋值

[[#解构赋值]]

const fn = (name, { age = 18, work = "front" } = {}) => {
  console.log(name, age, work) 
}

fn("zs") // zs 18 front
fn("zs", { age: 20 }) // zs 20 front

length 属性

const fn = (a, b, c) => {}
console.log(fn.length) // 3

const fn2 = (a, b, c = 1) => {}
console.log(fn2.length) // 2

可以使用 length 获取对象必传参数的个数

函数参数作用域

const x = 13;
function fn(x, y = x) {
  console.log(y);
}

fn(1); // 输出 1

在这个例子中,我们定义了一个函数 fn,它接受两个参数 xy,其中 y 的默认值为 x。当我们调用 fn(1) 时,函数内部的 x 被赋值为1,而由于没有传入 y 的值,所以 y 的默认值为函数内部的 x,因此 y 的值也为1,所以最终打印出来的是1

const x = 13;
function fn2(y = x) {
  console.log(y);
}

fn2(); // 输出 13

在这个例子中,我们定义了一个函数 fn2,它只接受一个参数 y,且 y 的默认值为外部定义的 x。当我们调用 fn2() 时,由于没有传入任何参数,所以 y 的默认值为外部的 x,即13,因此最终打印出来的是13。

function fn3(y = x) {
  const x = 2;
  console.log(y);
}

fn3(); // 输出:x is not defined

在这个例子中,我们定义了一个函数fn3,它只接受一个参数y,且y的默认值为外部定义的x。但是,在函数内部,我们又定义了一个局部变量x并赋值为2。当我们调用fn3()时,JavaScript会在函数体内查找默认参数y的值,但此时x还未定义,因此会报错x is not defined

这些例子展示了JavaScript函数参数作用域的一些特点:

  1. 函数内部的参数会覆盖外部定义的同名变量。
  2. 默认参数的作用域是在函数体内部。
  3. 函数内部定义的局部变量会遮蔽外部同名变量,但默认参数的作用域不受影响。

扩展运算符、剩余参数、arguments

详情见文档:

扩展运算符

剩余参数

arguments

箭头函数

() => {}

箭头函数与普通函数区别

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用 new
  2. 箭头函数内没有 arguments,可以用扩展运算符 ... 解决
  3. 箭头函数没有 this, 箭头函数的 this,始终指向父级上下文(箭头函数的 this 取决于定义位置 父级的上下文,跟使用位置没关系,普通函数 this 指向调用的那个对象)
  4. 箭头函数不能通过 call() 、 apply() 、bind() 方法直接修改它的 this 指向。(call、apply、bind 会默认忽略第一个参数,但是可以正常传参)
  5. 箭头函数没有原型属性

对象的扩展

属性简写

const name = "zs"
const age = 18
const person = {
  name,
  age,
}

console.log(person) // { name: 'zs', age: 18 }

属性名表达式

const name = "zs"
const age = 18
const s = 'school'
const person = {
  name,
  age,
  [s]: 'xx',
  study() {
    console.log(this.name + '爱学习')
  }
}

console.log(person) // { name: 'zs', age: 18, school: 'xx', study: [Function: study] }
person.study() // zs爱学习

Object.is ()

Object.is()静态方法确定两个值是否为相同值

// 案例 1:评估结果和使用 === 相同
Object.is(25, 25); // true
Object.is("foo", "foo"); // true
Object.is("foo", "bar"); // false
Object.is(null, null); // true
Object.is(undefined, undefined); // true
Object.is(window, window); // true
Object.is([], []); // false
const foo = { a: 1 };
const bar = { a: 1 };
const sameFoo = foo;
Object.is(foo, foo); // true
Object.is(foo, bar); // false
Object.is(foo, sameFoo); // true

// 案例 2: 带符号的 0
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(-0, -0); // true

// 案例 3: NaN
Object.is(NaN, 0 / 0); // true
Object.is(NaN, Number.NaN); // true

Object.is() 与 == 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 “” == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。

Object.is() 也不等价于 === 运算符。Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。=== 运算符(和 == 运算符)将数值 -0 和 +0 视为相等,但是会将 NaN 视为彼此不相等。

Object.assign()

Object.assign() 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { 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

遍历对象

  1. for... in
  2. Keys ()
  3. Object.getOwnPropertyNames ()
  4. Reflect.ownKeys ()

深拷贝、浅拷贝

基本类型数据保存在在内存中
引用类型数据保存在内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中

堆栈

深拷贝和浅拷贝的区别

1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

浅拷贝

数组浅拷贝:

// 直接遍历
function shallowCopy(arr) {
  const newArr = [];
  arr.forEach((item) => newArr.push(item));
  return newArr;
}
// slice
const arr2 = [1, 2, 3, 4, 5];
const newArr2 = arr2.slice();
//    concat() 合并空数组实现
const arr3 = [11, 22, 33, 44, 55];
const newArr3 = arr3.concat([]);

对象浅拷贝:

// 直接遍历
function shallowCopy(obj) {
  const newObj = {};
  for (let item in obj) {
    newObj[item] = obj[item];
  }
  return newObj;
}

// 使用拓展运算符
const obj2 = { name: "Bob", age: 17 };
const newObj2 = { ...obj2 };

深拷贝

用深拷贝最后要递归到全部是基本值,不然可能会陷入死循环/循环引用,导致栈溢出

TODO(待理解)⭐: 处理过的数据使用 map 结构缓存起来 >> 递归的时候碰到相同的数据 >> 直接使用缓存里面的

**1. 先转换成字符串,在转换成(数组/对象) JSON.parse(JSON.stringify(XXXX))

[!tip] 注意
缺点:不能拷贝里面的函数

const array = [{ number: 1 }, { number: 2 }, { number: 3 }];
const str = JSON.stringify(array);
const copyArray = JSON.parse(str);

2、递归实现简单的深拷贝:

const cloneDeep = (target) => {
  const getType = (data) => {
    return Object.prototype.toString.call(data).slice(8, -1)
  }

  const targetType = getType(target)

  if (targetType === "Object") {
    const result = {}
    Object.getOwnPropertyNames(target).forEach(key => {
      result[key] = cloneDeep(target[key])
    })
    return result
  } else if (targetType === "Array") {
    return target.map(item => deepClone(item))
  } else {
    return target
  }
}

3、解构赋值法实现一层拷贝

4、Object.assign() 实现一层深拷贝

5、 lodash第三方库

在引用类型的情况下,拷贝分为浅拷贝和深拷贝:

  • 浅拷贝复制的是对象的引用,因此当属性为对象时,浅拷贝只复制一层,导致两个对象指向同一个地址。
  • 深拷贝则是递归地复制对象的所有层次,包括属性为对象的情况,因此深拷贝会创建一个全新的对象栈,使得两个对象指向不同的地址。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值