JavaScript-Advanced Days1 Notes

1、为什么如下代码,最后一行的i打印不出,但是第1个循环外面的i却能够打印出来?

  <script>
    for (var i = 1; i <= 3; i++) {
      // 块作用域
      console.log(i)
    }
    console.log(i)


    for (let i = 1; i <= 3; i++) {
      // 块作用域
      console.log(i)
    }
    for (let i = 1; i <= 3; i++) {
      // 块作用域
      console.log(i)
    }
    if (true) {
      let i = 10
    }
    console.log(i)
  </script>

在这段代码中,varlet 的作用范围不同,这导致了最后一行的 i 打印不出来,而第一个循环外的 i 却可以打印出来。

关键点在于 varlet 的作用域:

  1. var 是函数作用域或全局作用域

    • 当你使用 var 声明变量时,即使它是在 for 循环中或 if 语句块中声明的,变量仍然会在整个函数或全局范围内生效。因此,即使 i 是在 for 循环中声明的,它在循环外依然是可访问的。
  2. let 是块作用域

    • let 声明的变量具有块作用域。也就是说,变量仅在当前的代码块 {} 内有效。一旦离开该块,变量就不再可用。

总结:

  • var 声明的变量具有全局或函数作用域,因此即使在块中声明,变量在块外仍然可以访问。
  • let 声明的变量具有块作用域,因此在块(例如 for 循环或 if 语句)外无法访问。

因此,最后一行打印 i 时,由于使用的是 let 声明的变量,超出了它的作用域,导致无法打印。而使用 var 时,变量 i 的作用域是全局的,因此在循环外仍然可以访问并打印。

 

2、如何理解下面代码,用垃圾回收机制的角度

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    for (let i = 1; i <= 3; i++) {

    }
    let num = 10
    function fn() {
      const str = 'andy'
      // str = 'lily'
      console.log(str)
    }
    fn() 
    fn() 
    fn()  
  </script>
</body>

</html>

在这段代码中,垃圾回收机制的作用是自动管理内存,释放那些不再被引用或使用的对象。浏览器的垃圾回收器会定期查找那些没有引用的变量和对象,并释放它们占用的内存空间。

垃圾回收机制概述

JavaScript 使用的是自动垃圾回收机制,最常用的算法是标记清除(Mark-and-Sweep)。当变量或对象不再被引用时,垃圾回收器会将其标记为"可回收",随后会清除这些不再使用的变量,释放内存。

如何在你的代码中发挥作用:

  1. for 循环中的变量 i

    • 在循环中使用了 let i = 1; i <= 3; i++,由于 let 是块作用域的,i 在循环结束后会被回收。
    • for 循环结束时,i 变量不再可用,因此它的内存会被垃圾回收机制自动释放。
  2. num = 10

    • 变量 num 被声明并赋值为 10,它是在全局作用域中声明的,因此在脚本运行时会一直存在,除非显式地重新分配或者覆盖它。
  3. 函数 fn 和局部变量 str

    • 每次调用函数 fn() 时,都会创建一个新的局部变量 str,并在函数执行结束后这个局部变量将不再被引用。
    • 垃圾回收器在函数 fn 执行结束后会标记 str 变量为可回收对象,并释放其内存。
    • fn() 被调用三次时,虽然每次都会创建新的 str 变量,但每次函数调用结束后,之前的 str 都会被垃圾回收,不会造成内存泄漏。
  4. 多次调用函数 fn()

    • 每次调用函数 fn() 都会创建独立的上下文环境,其中包含局部变量 str。一旦函数调用结束,局部变量 str 就会失效,随后会被垃圾回收。
    • 由于变量 str 在每次调用时都是局部的且不会被外部引用,所以它在函数结束后可以安全地回收。

垃圾回收机制作用总结:

  • for 循环的 i 在块结束后就不再可访问,因此可以回收。
  • 局部变量 str 在每次 fn 函数执行结束后失效,并被回收。
  • 全局变量 num 一直存活,因为它没有被覆盖或者释放。

垃圾回收机制的关键是,它会自动释放那些不再被使用或引用的变量,避免内存泄漏,确保程序的内存占用始终处于合理状态。

 

3、如何理解如下代码,变量提升的概念

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    // 1. 把所有var声明的变量提升到 当前作用域的最前面
    // 2. 只提升声明, 不提升赋值
    var num
    console.log(num + '')
    num = 10 
    console.log(num) 

    function fn() {
      console.log(num)
      var num = 10
    }
    fn()
  </script>
</body>

</html>

变量提升的概念与作用

变量提升(Hoisting) 是 JavaScript 中的一个机制,在代码执行之前,变量和函数声明会被移动到其所在作用域的顶部。注意,只有声明被提升,赋值并不会被提升。只有var有变量提升,但这其实不好事,是JS的一大缺陷。

变量提升有两个关键点:

  1. 声明被提升:JavaScript 引擎会把变量和函数的声明提升到当前作用域的顶部(无论是全局作用域还是函数作用域)。
  2. 赋值不提升:虽然声明会被提升,但变量赋值依然发生在代码运行时的原位置。

代码分析

var num;
console.log(num + '件'); // 输出: undefined件
num = 10;
console.log(num); // 输出: 10

1. 变量 num 的提升

  • 第一行var num 实际上被提升到了代码块的顶部。
  • console.log(num):因为声明已经被提升,但赋值还没有发生,因此此时 num 的值是 undefined,输出 undefined件
  • num = 10:此时 num 被赋值为 10,然后下一行输出 10

变量提升的实质是,var num; 会被看作是在代码的最开始定义,但没有赋值。

2. 函数内的变量提升

function fn() {
  console.log(num);
  var num = 10;
}
fn();
  • var num = 10 在函数作用域内声明,函数内的 var num 会被提升到函数的顶部。
  • 实际执行过程类似于:
function fn() {
  var num; // 提升了声明
  console.log(num); // undefined,因为此时还没有赋值
  num = 10; // 在这时才进行赋值
}
  • 因此 console.log(num) 输出的是 undefined,而不是全局变量 num 的值。

通俗解释

变量提升就像是在你编写的代码中,所有用 var 声明的变量,都会被“偷偷地”提到代码的最上面,但只有声明会被提上去,赋值不会。

举例说明

console.log(a); // 输出:undefined
var a = 5;
console.log(a); // 输出:5

这段代码的执行顺序实际上是这样的:

var a;
console.log(a); // 输出:undefined
a = 5;
console.log(a); // 输出:5

为什么有变量提升?

变量提升的设计可以让 JavaScript 在解释执行代码时,不会因为变量的声明顺序而报错。这给了开发者在写代码时更大的灵活性。

总结

  • 变量提升 是 JavaScript 的机制,var 声明的变量会被提升到作用域的最顶端。
  • 只有声明被提升,赋值留在原位置。
  • 函数内的变量声明同样会提升,但赋值不会影响提升前的行为。

 

4、如下代码,为什么要加上...才能返回对应的最大、最小值、数组合并呢?

  <script>
    const arr1 = [1, 2, 3]
    // 展开运算符 可以展开数组
    // console.log(...arr)

    // console.log(Math.max(1, 2, 3))
    // ...arr1  === 1,2,3
    // 1 求数组最大值
    console.log(Math.max(...arr1)) // 3
    console.log(Math.min(...arr1)) // 1
    // 2. 合并数组
    const arr2 = [3, 4, 5]
    const arr = [...arr1, ...arr2]
    console.log(arr)

  </script>

关于 ... 展开运算符(spread operator)

在 JavaScript 中,... 被称为展开运算符它的作用是将数组或对象“展开”为单独的元素或属性。这在函数调用、数组操作和对象合并等场景中非常有用。

为什么要加 ... 才能返回最大、最小值?

示例代码:

const arr1 = [1, 2, 3];
console.log(Math.max(...arr1)); // 3
console.log(Math.min(...arr1)); // 1

理解:

  • Math.max()Math.min() 这两个函数需要一组独立的数值作为参数,而不是数组。如果直接传入数组(比如 Math.max([1, 2, 3])),它不会工作,因为这些函数期望接收到一组单独的数值,而不是一个数组。

  • ...arr1 是展开运算符,它将数组 arr1 中的元素 [1, 2, 3] 展开成独立的数值,等同于 Math.max(1, 2, 3)。这就是为什么要加 ...,它能够将数组展开成独立的参数传递给函数。

具体说明:

  • Math.max(...arr1) 会将数组 [1, 2, 3] 展开为 1, 2, 3,并返回其中的最大值 3
  • Math.min(...arr1) 则返回最小值 1

为什么要加 ... 来合并数组?

示例代码:

const arr2 = [3, 4, 5];
const arr = [...arr1, ...arr2];
console.log(arr); // [1, 2, 3, 3, 4, 5]

理解:

  • ... 展开运算符在数组合并时非常方便,它将 arr1arr2 中的元素展开为独立的元素,并通过 [...] 将这些展开的元素重新组合成一个新的数组。
  • [...arr1, ...arr2] 的效果是将两个数组展开并合并,等同于 [1, 2, 3, 3, 4, 5]

不使用展开运算符的替代方法:

你也可以使用 concat() 来合并数组:

const arr = arr1.concat(arr2);
console.log(arr); // [1, 2, 3, 3, 4, 5]

不过,使用展开运算符通常更加简洁和现代。

总结:

  • 展开运算符 ... 可以将数组中的元素“展开”为单独的值,这对于函数调用和数组合并等操作非常有用。
  • 在使用 Math.max()Math.min() 时,展开运算符能够将数组中的元素作为单独的数值传递给函数。
  • 在数组合并时,... 展开运算符使得合并更加简洁和直观。

 

5、为什么这个函数返回的是 {uname: 刘德华} 按道理直觉上理解不应该是返回 {刘德华: 刘德华}?

    // 5. 箭头函数可以直接返回一个对象
    const fn = (uname) => ({ uname: uname })
    console.log(fn('刘德华'))

在你的代码中,函数返回的是 {uname: 刘德华},这是因为你定义的返回对象使用了属性名 uname,并将传入的参数 uname 的值赋给了该属性。以下是详细解释:

代码回顾

const fn = (uname) => ({ uname: uname });
console.log(fn('刘德华')); // {uname: '刘德华'}

详细说明

  1. uname: uname 语法:

    • 左边的 uname 是对象的属性名
    • 右边的 uname 是箭头函数的参数,即传入的值 '刘德华'

    所以 { uname: uname } 实际上是一个对象字面量,表示对象的属性名uname属性值'刘德华'

  2. 简写形式如果对象的属性名和属性值的变量名相同,JavaScript 允许使用简写形式,省略右边的赋值变量:

const fn = (uname) => ({ uname });

这和 ({ uname: uname }) 是等效的,简化了代码书写。

为什么不是 {刘德华: 刘德华}

如果你想要返回 {刘德华: '刘德华'},即将属性名动态地设置为参数的值,可以这样改写代码:

在这里,方括号 [] 表示属性名是动态的,[uname] 会将传入的值 '刘德华' 作为对象的属性名

总结:

  • { uname: uname } 是在创建一个对象,其中属性名是 uname,属性值是传入的参数 '刘德华'
  • 如果你想要动态地将参数值作为对象的属性名,需要使用 [uname] 这种动态属性语法。

 

6、为什么如下代码,返回的结果中有个f, {name: 'andy', sayHi: ƒ}, 这个是什么?固定的表示吗?

    // 对象方法里面的this
    const obj = {
      name: 'andy',
      sayHi: function () {
        console.log(this)  // obj
      }
    }
    obj.sayHi()

在你的代码中,返回的结果 {name: 'andy', sayHi: ƒ} 中的 ƒ 并不是一种固定的表示法,而是浏览器开发工具(通常是 Chrome)用于简化显示函数的形式

它表示对象中的某个属性是一个函数(function),具体而言,ƒ 是 "function" 的缩写,表示 sayHi 是一个方法(函数)。

详细说明

  1. 代码分析

const obj = {
  name: 'andy',
  sayHi: function () {
    console.log(this)  // obj
  }
}
obj.sayHi()
  1. 在这个对象 obj 中,有两个属性:

    • name: 'andy':这是一个字符串属性。
    • sayHi: function () {...}:这是一个方法(即函数作为对象的属性)。
  2. ƒ 的含义当你在控制台查看对象时,浏览器将函数属性显示为 ƒ 以表明该属性是一个函数。例如,在 {name: 'andy', sayHi: ƒ} 中:

    • name: 'andy' 是普通的键值对。
    • sayHi: ƒ 意味着 sayHi 是一个方法(函数)。

    实际上,ƒ 仅仅是浏览器(例如 Chrome)显示的一种简化符号。在代码层面,它依旧是标准的 JavaScript 函数。你可以执行 obj.sayHi(),并且它会正确运行。

  3. this 的作用

    • 当你调用 obj.sayHi() 时,this 在函数内部会指向调用它的对象 obj
    • 所以当你 console.log(this) 时,它会打印出整个 obj 对象,形如 {name: 'andy', sayHi: ƒ},其中 sayHi 作为一个方法(函数)被简化显示为 ƒ

示例:

在 Chrome 浏览器开发者工具中,你可能会看到类似这样的结果:

{name: 'andy', sayHi: ƒ}

表示对象 obj 有一个 name 属性,以及一个 sayHi 函数。

总结:

  • ƒ 是浏览器对函数的一种简化表示。
  • 它表明对象中的属性 sayHi 是一个函数。
  • 这并不是 JavaScript 固定的表示法,而是调试工具中的简写。

 

7、关于如下代码为什么必须要加封号的理解。

  <script>
    // 1. 立即执行函数要加
    (function () { })();
    (function () { })();

    // 2. 使用数组的时候/
    const arr = [1, 2, 3]
    const str = 'pink';
    [1, 2, 3].map(function (item) {
      console.log(item)
    })
  
  // 3. 解构赋值 let a = 1 let b = 2 ;[b, a] = [a, b] console.log(a, b) </script>

通过具体的例子,详细解释加分号和不加分号的区别,特别是在 JavaScript 的自动分号插入 (ASI) 机制下。

例子 1:立即执行函数

不加分号的情况

var a = 1
(function() { console.log('立即执行函数') })()

如果你不加分号,JavaScript 会认为 (function() { console.log('立即执行函数') })() 是要和前面的 var a = 1 连接在一起。因此,它会试图将整个代码理解为这样:

var a = 1(function() { console.log('立即执行函数') })()

这就变成了:1 后面跟着立即调用一个函数,而这是非法的,因为 1 不是一个可以调用的函数。所以浏览器会报错。

 

加分号的情况

var a = 1;
(function() { console.log('立即执行函数') })()

加了分号后,JavaScript 明确知道 var a = 1 这一句已经结束,接下来是一句新的代码 (function() { console.log('立即执行函数') })(),这才会正确执行。

不加分号的后果:代码语法错误,无法执行。

 

例子 2:数组作为新一行的开头

不加分号的情况

const arr = [1, 2, 3]
[1, 2, 3].map(function(item) {
  console.log(item)
})

JavaScript 在这里会误以为 [1, 2, 3] 是在跟前一行 arr 做某种操作,具体来说,JavaScript 会试图将 [1, 2, 3] 当作数组索引器来看,

也就是认为 [1, 2, 3]arr[1] 这种格式中的一部分。这就导致代码运行时没有按预期打印出数组内容,而是可能会报错。

 

加分号的情况

const arr = [1, 2, 3];
[1, 2, 3].map(function(item) {
  console.log(item)
})

加上分号后,JavaScript 就知道 const arr = [1, 2, 3] 已经结束,接下来 [1, 2, 3] 是一个独立的数组操作。这样就不会产生错误。

不加分号的后果:代码逻辑错误,结果不如预期。

 

例子 3:解构赋值

不加分号的情况

let a = 1
let b = 2
[b, a] = [a, b]
console.log(a, b)

在这种情况下,JavaScript 会试图把 [b, a] 解释为前一行 let b = 2 的延续部分,就像它在做数组索引一样。例如,它可能会误以为你想这样写:

let b = 2[b, a] = [a, b]

显然这是不合法的,最终导致报错。

 

加分号的情况

let a = 1;
let b = 2;
[b, a] = [a, b];
console.log(a, b)

加上分号后,JavaScript 明确知道 let b = 2 这一句结束了,接下来 [b, a] = [a, b] 是一个独立的解构赋值操作。所以不会报错。

不加分号的后果:代码语法错误,无法执行。

总结:

  1. 不加分号时,JavaScript 有时候会把两行代码误解为同一个操作,导致语法错误或者逻辑错误。
  2. 加分号能避免这种误解,明确告诉 JavaScript 哪里是一句代码的结束,哪里是新的一句代码。

自动分号插入机制并不是总能正确地补上分号,所以在某些情况下,例如立即执行函数、数组操作或解构赋值时,必须手动加分号,以避免代码执行错误。

 

8、如下代码的.filter()方法如何理解?

    // 2. 过滤筛选  
    document.querySelector('.filter').addEventListener('click', e => {
      // e.target.dataset.index   e.target.tagName
      const { tagName, dataset } = e.target
      // 判断 
      if (tagName === 'A') {
        // console.log(11) 
        // arr 返回的新数组 
        let arr = goodsList 
        if (dataset.index === '1') {
          arr = goodsList.filter(item => item.price > 0 && item.price <= 100)
        } else if (dataset.index === '2') {
          arr = goodsList.filter(item => item.price >= 100 && item.price <= 300)
        } else if (dataset.index === '3') {
          arr = goodsList.filter(item => item.price >= 300)
        } 
        // 渲染函数
        render(arr)

filter() 是 JavaScript 数组中的一个方法,它用于 筛选数组中的元素,并根据条件返回一个新的数组。这个新数组只包含满足条件的元素。

filter() 方法的作用和工作方式:

  1. 作用filter() 方法会遍历数组的每个元素,调用一个回调函数,检查每个元素是否符合条件。如果该元素符合条件(回调函数返回 true),则将这个元素放入新数组中。最后返回一个包含所有符合条件元素的数组。

  2. 语法

let newArray = array.filter(callback(element, index, array))
    • callback: 这是一个返回布尔值的函数,决定元素是否被保留。
      • element: 当前正在处理的数组元素。
      • index(可选):当前元素的索引。
      • array(可选):原始数组。
    • newArray: 返回的新数组,包含所有通过 callback 函数筛选的元素。

通俗解释:

filter() 就像是在给数组中的每个元素做一个筛选测试,只有通过测试的元素才会被保留下来。

例如如下例子,理解鹈

  <script>
    const arr = [10, 20, 30]
    // const newArr = arr.filter(function (item, index) {
    //   // console.log(item)
    //   // console.log(index)
    //   return item >= 20
    // })
    // 返回的符合条件的新数组

    const newArr = arr.filter(item => item >= 20)
    console.log(newArr)
  </script>

 

posted @ 2024-09-16 09:35  AlphaGeek  阅读(21)  评论(0)    收藏  举报