Skip to content

Javascript Advanced

1.作用域

了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。

  • 作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,
  • 作用域分为:
    • 局部作用域
    • 全局作用域

1.2局部作用域

  • 局部作用域分为函数作用域和块作用域
  • 局部作用域是指函数内部定义的变量,函数执行完毕后,该变量就不再存在了。
  • 局部作用域的变量是有作用域链的,查找变量时,会从当前作用域开始,向上级作用域查找,直到全局作用域。

1.2.1函数作用域

在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

js
//写出一个函数作用域的例子
function foo() {
  let a = 1;
  let b = 2;
  console.log(a);
  console.log(b);
}
console.log(a);
console.log(b);
//写出一个函数作用域的例子
function foo() {
  let a = 1;
  let b = 2;
  console.log(a);
  console.log(b);
}
console.log(a);
console.log(b);

总结:

  1. 函数内部声明的变量,在函数外部无法被访问
  2. 函数的参数也是函数内部的局部变量
  3. 不同函数内部声明的变量无法互相访问
  4. 函数执行完毕后,函数内部的变量实际被清空了

1.2.2块作用域

在JavaScript中使用{ }包裹的代码称之为代码块,代码块内部声明外部变量将[有可能]无法被访问

js
//写一个相关的程序
for(let i = 0; i < 10; i++){
    //正常
    console.log(i);
}
//报错
console.log(i)
//写一个相关的程序
for(let i = 0; i < 10; i++){
    //正常
    console.log(i);
}
//报错
console.log(i)

总结:

  1. let 声明的变量会产生块作用域,var 不会产生块作用域
  2. const 声明的常量也会产生块作用域
  3. 不同代码块之间的变量无法互相访问
  4. 推荐使用 let 或 const

1.3全局作用域

<script> 标签 和 .js 文件 的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。 全局作用域中声明的变量,任何其它作用域都可以被访问

WARNING

注意:

  1. 为 window 对象动态添加的属性默认也是全局的
  2. 函数中未使用任何关键字声明的变量为全局变量
  3. 尽可能少的声明全局变量,防止全局变量被污染

1.4作用域链

作用域链本质上是底层的变量查找机制

  • 在函数被执行时,会优先查找当前函数作用域中查找变量

  • 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域

js
//全局作用域
let a =1
let b =2
//局部作用域
function f(){
    let a =1
    function(){
        a = 2
        console.log(a)
    }
    g()
}
f()
//全局作用域
let a =1
let b =2
//局部作用域
function f(){
    let a =1
    function(){
        a = 2
        console.log(a)
    }
    g()
}
f()

总结:

  1. 嵌套关系的作用域串联起来形成了作用域链
  2. 相同作用域链中按着从小到大的规则查找变量
  3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域

1.5JS垃圾回收机制

垃圾回收机制(Garbage Collection) 简称 GC JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。 正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题 但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况 不再用到的内存,没有及时释放,就叫做内存泄漏

1.5.1内存生命周期

JS环境中分配的内存, 一般有如下生命周期

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数等
  3. 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
  4. 说明:
    • 全局变量一般不会回收(关闭页面回收);
    • 一般情况下局部变量的值, 不用了, 会被自动回收掉

内存泄漏:程序中分配的内存由于某种原因程序未释放或者无法释放叫做内存泄漏

1.5.2垃圾回收机制-说明

堆栈空间分配区别:

  1. (操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
  2. (操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。 介绍两种常见的浏览器垃圾回收算法: 引用计数法标记清除法
引用计数

IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象 算法:

  1. 跟踪记录被引用的次数
  2. 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
  3. 如果减少一个引用就减1 --
  4. 如果引用次数是0 ,则释放内存
标记清除

现代的浏览器已经不再使用引用计数算法了。 现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。 核心:

  1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
  2. 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
  3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

1.6闭包

概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域 简单理解:闭包 = 内层函数 + 外层函数的变量 先看个简单的代码:

js
function outer(){
    const a = 1
    function f(){
        console.log(a)
    }
    f()
}
outer()
function outer(){
    const a = 1
    function f(){
        console.log(a)
    }
    f()
}
outer()

闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量闭包的基本格式:

js
//闭包基本格式
function outer(){
    const a = 1
    function f(){
        console.log(a)
    }
    return f
}
let f = outer()
f()
//闭包基本格式
function outer(){
    const a = 1
    function f(){
        console.log(a)
    }
    return f
}
let f = outer()
f()

简写:

js
//闭包代码简写
function outer(){
    const a = 1
    return function(){
        console.log(a)
    }
}
let f = outer()
f()
//闭包代码简写
function outer(){
    const a = 1
    return function(){
        console.log(a)
    }
}
let f = outer()
f()

闭包案例:

js
function count(){
  let i =0
  function fn(){
    i +=1
    console.log(i);
  }
  return fn
}
const func = count()
console.log(func())
function count(){
  let i =0
  function fn(){
    i +=1
    console.log(i);
  }
  return fn
}
const func = count()
console.log(func())
js
function outerFunction(x) {
 return function innerFunction(y) {
   return x + y
 }
}

let add = outerFunction(10)
console.log(add(5)) // 输出 15
function outerFunction(x) {
 return function innerFunction(y) {
   return x + y
 }
}

let add = outerFunction(10)
console.log(add(5)) // 输出 15

在这个例子中,x 是形参,它是 outerFunction 函数的参数。x 的值在 outerFunction 函数的作用域中被分配,然后被传递给 innerFunction 函数。即使 outerFunction 函数已经返回了,x 的值仍然存在于其词法环境内,因此 innerFunction 函数可以访问它。

outerFunction 函数返回了一个内部函数 innerFunction,它可以在 outerFunction 函数的作用域中访问变量 x。当我们调用 outerFunction(10) 时,返回的是一个函数,即 innerFunction。然后我们将这个返回的函数赋值给变量 add,这样我们就可以像调用普通函数一样调用 add(5),得到结果 15。

在这个例子中,innerFunction 函数可以访问 outerFunction 函数的变量 x,即使 outerFunction 函数已经返回了,它的词法环境也仍然存在。这就是闭包的概念。

1.7变量提升

变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量

注意:

  1. 变量在未声明即被访问时会报语法错误
  2. 变量在var声明之前即被访问,变量的值为 undefined
  3. let/const 声明的变量不存在变量提升
  4. 变量提升出现在相同作用域当中
  5. 实际开发中推荐先声明再访问变量

WARNING

把所有var声明的变量提升到 当前作用域的最前面 只提升声明,不提升赋值

2.函数进阶

2.1函数提升

函数提升与变量提升比较类似,是指函数在声明之前即可被调用。 总结:

  1. 函数提升能够使函数的声明调用更灵活
  2. 函数表达式不存在提升的现象
  3. 函数提升出现在相同作用域当中

WARNING

会把所有函数声明提升到当前作用域的最前面

只提升函数声明,不提升函数调用

函数表达式必须先声明和赋值后调用,否则报错

2.2函数参数

2.2.1动态参数

arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参

js
function foo(a, b, c) {
  console.log(arguments);
}
function foo(a, b, c) {
  console.log(arguments);
}
js
//再来一个
function getSum(){
    let sum =0
    for(let i = 0; i<arguments.length;i++){
        sum += arguments[i]
    }
    return sum
}
console.log(getSum(1,2,3,6,7,9))
//再来一个
function getSum(){
    let sum =0
    for(let i = 0; i<arguments.length;i++){
        sum += arguments[i]
    }
    return sum
}
console.log(getSum(1,2,3,6,7,9))

总结:

  1. arguments 是一个伪数组,只存在于函数中
  2. arguments 的作用是动态获取函数的实参
  3. 可以通过for循环依次得到传递过来的实参

2.2.2剩余参数

剩余参数允许我们将一个不定数量的参数表示为一个数组

  1. ... 是语法符号,置于最末函数形参之前,用于获取多余的实参
  2. 借助 ... 获取的剩余实参,是个真数组

提倡多使用 剩余参数

js
//剩余参数示例
function foo(a,b,...args){
    console.log(a)
    console.log(b)
    console.log(args)
}
foo(1,2,3,4,5,6,7,8,9)
//剩余参数示例
function foo(a,b,...args){
    console.log(a)
    console.log(b)
    console.log(args)
}
foo(1,2,3,4,5,6,7,8,9)

2.3箭头函数 important

2.3.1基本语法

函数中只有一行代码的时候,可以省略大括号

  • 语法1: 没有参数不能省略小括号( )

    js
    const fn = () => {
      console.log("hello world")
    }
    fn()
    const fn = () => {
      console.log("hello world")
    }
    fn()
  • 语法2:

    js
    const fn = () => console.log("hello world")
    fn()
    const fn = () => console.log("hello world")
    fn()
  • 语法2:

    js
    const fn = (x) => console.log(x)
    fn(1)
    const fn = (x) => console.log(x)
    fn(1)
  • 语法3: 一个参数时,可以省略小括号( )

    js
    const fn = x => console.log(x)
    fn(1)
    const fn = x => console.log(x)
    fn(1)
  • 语法4: 有两个或多个参数时,小括号( )不能省略

    js
    const fn = (x,y) => console.log(x+y)
    fn(1,4)
    const fn = (x,y) => console.log(x+y)
    fn(1,4)
  • 语法5:

    js
    const fn = (x,y) => {
      return x+y
    }
    console.log(fn(1,4))
    const fn = (x,y) => {
      return x+y
    }
    console.log(fn(1,4))
  • 语法6: 只有一行代码可以省略return

    js
    // 定义一个函数fn,参数为x,y
    const fn = (x,y) => x+y
    // 调用函数fn,参数为1,4
    console.log(fn(1,4))
    // 定义一个函数fn,参数为x,y
    const fn = (x,y) => x+y
    // 调用函数fn,参数为1,4
    console.log(fn(1,4))
  • 语法7: 注意:如果返回的是一个对象,需要在大括号{ }外加一个小括号( ),否则会报错

    js
    // 定义一个函数,接收一个参数uname,返回一个对象,对象有name属性,值为uname
    const fn = uname => ({name: uname})
    // 调用函数fn,传入参数'你好',打印出返回的对象
    console.log(fn('你好'))
    // 定义一个函数,接收一个参数uname,返回一个对象,对象有name属性,值为uname
    const fn = uname => ({name: uname})
    // 调用函数fn,传入参数'你好',打印出返回的对象
    console.log(fn('你好'))
  1. 箭头函数属于表达式函数,因此不存在函数提升
  2. 箭头函数只有一个参数时可以省略圆括号 ()
  3. 箭头函数函数体只有一行代码时可以省略花括号 {},并自动做为返回值被返回
  4. 加括号的函数体返回对象字面量表达式

2.3.2箭头函数参数

  1. 普通函数有arguments 动态参数
  2. 箭头函数没有 arguments 动态参数,但是有 剩余参数 ..args
js
// 定义一个函数,参数为可变参数,用于求和
const getSum = (...args)=> {
  // 声明一个变量sum,用于存放求和结果
  let sum =0
  // 遍历参数数组,将参数数组中的每一项相加
  for(let i=0;i<args.length;i++){
    sum+=args[i]
  }
  // 返回求和结果
  return sum
}
const sum = getSum(33,66,77)
console.log(sum);
// 定义一个函数,参数为可变参数,用于求和
const getSum = (...args)=> {
  // 声明一个变量sum,用于存放求和结果
  let sum =0
  // 遍历参数数组,将参数数组中的每一项相加
  for(let i=0;i<args.length;i++){
    sum+=args[i]
  }
  // 返回求和结果
  return sum
}
const sum = getSum(33,66,77)
console.log(sum);

2.3.3箭头函数this

在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。

使用箭头函数前需要考虑函数中 this 的值,事件回调函数使用箭头函数时,this 为全局的 window, 因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数

js
//箭头函数此时this指向了window
btn.addEventListener('click',()=>{
    console.log(this)
})
//箭头函数此时this指向了window
btn.addEventListener('click',()=>{
    console.log(this)
})
js
//普通函数此时this指向了DOM对象
btn.addEventListener('click',function(){
    console.log(this)
})
//普通函数此时this指向了DOM对象
btn.addEventListener('click',function(){
    console.log(this)
})

2.3.4forEach方法

  • forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数

  • 主要使用场景: 遍历数组的每个元素

  • 语法:

    js
    arr.forEach(function(当前元素, 当前元素索引){
        //函数体
    })
    arr.forEach(function(当前元素, 当前元素索引){
        //函数体
    })
    js
    const arr = ["你好","你好啊","来测"]
    arr.forEach((item,index)=>{
        console.log(item,index)
    })
    const arr = ["你好","你好啊","来测"]
    arr.forEach((item,index)=>{
        console.log(item,index)
    })

注意:

  1. forEach 主要是遍历数组
  2. 参数当前数组元素是必须要写的, 索引号可选。

2.3.5filter方法

image-20231203020452210

  • filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素

  • 主要使用场景: 筛选数组符合条件的元素并返回筛选之后元素的新数组

  • 语法:

    js
    arr.filter(function(当前元素, 当前元素索引)){
        //函数体
        return true/false
    }
    arr.filter(function(当前元素, 当前元素索引)){
        //函数体
        return true/false
    }
    js
    const arr = [1,2,3,4,5]
    const newArr = arr.filter((item,index)=>{
        return item>2
    })
    console.log(newArr)
    // 输出结果: [3,4,5]
    const arr = [1,2,3,4,5]
    const newArr = arr.filter((item,index)=>{
        return item>2
    })
    console.log(newArr)
    // 输出结果: [3,4,5]

递归

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数

  • 简单理解:函数内部自己调用自己, 这个函数就是递归函数
  • 递归函数的作用和循环效果类似
  • 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
js
let i =1
function fn(){
    console.log(i)
    i++
    if(i<10){
        fn()
    }
    return
}
fn()
console.log(i)
let i =1
function fn(){
    console.log(i)
    i++
    if(i<10){
        fn()
    }
    return
}
fn()
console.log(i)

3.解构赋值

结构赋值是一种快速为变量赋值的简介语法,本质上仍是为变量赋值 分为:

  1. 数组解构
  2. 对象解构

3.1数组解构

数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法

基本语法:

  1. 赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
  2. 变量的顺序对应数组单元值的位置依次进行赋值操作
js
//写一个数组解构的示例
const arr = [1,2,3,4,5]
const [a,b,c,d,e] = arr
console.log(a,b,c,d,e)
// 输出结果: 1 2 3 4 5
//写一个数组解构的示例
const arr = [1,2,3,4,5]
const [a,b,c,d,e] = arr
console.log(a,b,c,d,e)
// 输出结果: 1 2 3 4 5

交换变量案例:

js
let a =1
let b =2;//这里的;必须加上,否则会报错
[a,b]=[b,a]
console.log(a,b)
let a =1
let b =2;//这里的;必须加上,否则会报错
[a,b]=[b,a]
console.log(a,b)

DANGER

注意: js 前面必须加分号情况

  1. 立即执行函数

    js
    (function(){})();
    //或者
    ;(function(){})()
    (function(){})();
    //或者
    ;(function(){})()
  2. 数组解构

    js
    //以数组开头的,特别是前面有语句的一定注意加分号
    let a =1
    let b =2
    ;[a,b] = [1,2]
    //以数组开头的,特别是前面有语句的一定注意加分号
    let a =1
    let b =2
    ;[a,b] = [1,2]

TIP

下面是一些解构赋值的常见用法

  • 1.变量多 单元值少的情况:

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

    变量的数量大于单元值数量时,多余的变量将被赋值为 undefined

  • 2.变量少 单元值多的情况:

    js
    let [a,b,c] = [1,2,3,4,5]
    console.log(a,b,c)
    let [a,b,c] = [1,2,3,4,5]
    console.log(a,b,c)
  • 3.利用剩余参数解决变量少 单元值多的情况:

    js
    let [a,b,...c] = [1,2,3,4,5]
    console.log(a,b,c)
    let [a,b,...c] = [1,2,3,4,5]
    console.log(a,b,c)

    这里利用剩余参数c,将数组的剩余元素赋值给变量c,返回一个真数组

  • 4.防止有undefined传递单元值的情况,可以设置默认值:

    js
    let [a=7,b=10,c=3,d=6] = [1,2]
    console.log(a) //1
    console.log(b) //2
    console.log(c) //3
    console.log(d) //6
    let [a=7,b=10,c=3,d=6] = [1,2]
    console.log(a) //1
    console.log(b) //2
    console.log(c) //3
    console.log(d) //6
  • 5.按需导入,忽略某些返回值:

    js
    let [a,,b,c]=[1,2,3,4,5]
    console.log(a) //1
    console.log(b) //3
    console.log(c) //4
    let [a,,b,c]=[1,2,3,4,5]
    console.log(a) //1
    console.log(b) //3
    console.log(c) //4
  • 6.支持多维数组的结构:

    js
    let [a,b,c] = [[1,2],[3,4],[5,6]]
    console.log(a) //[1,2]
    console.log(b) //[3,4]
    console.log(c) //[5,6]
    let [a,b,c] = [[1,2],[3,4],[5,6]]
    console.log(a) //[1,2]
    console.log(b) //[3,4]
    console.log(c) //[5,6]

3.2对象解构

对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法

  1. 基本语法:
  2. 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
  3. 对象属性的值将被赋值给与属性名相同的变量,属性名和变量名必须一致
  4. 注意解构的变量名不要和外面的变量名冲突否则报错
  5. 对象中找不到与变量名一致的属性时变量值为 undefined
js
const user = {
  name: '张三',
  age: 18,
  sex: '男',
  address: '北京市海淀区',
  hobbies: ['吃饭', '睡觉', '打豆豆'],
  friends: [
    {
      name: '李四',
      age: 18,
      sex: '男',
      address: '北京市海淀区',
      hobbies: []
    }
  ]
}
const {name,age} = user
//等价于 const name = user.name
console.log(name) //张三
console.log(age) //18
const user = {
  name: '张三',
  age: 18,
  sex: '男',
  address: '北京市海淀区',
  hobbies: ['吃饭', '睡觉', '打豆豆'],
  friends: [
    {
      name: '李四',
      age: 18,
      sex: '男',
      address: '北京市海淀区',
      hobbies: []
    }
  ]
}
const {name,age} = user
//等价于 const name = user.name
console.log(name) //张三
console.log(age) //18
  • 1.给新的变量名赋值: 可以从一个对象中提取变量并同时修改新的变量名 冒号表示“什么值:赋值给谁 旧变量名:新变量名,使用的时候使用新变量名

    js
    const user = {
      name: '张三',
      age: 18
    }
    const {name: userName,age: userAge} = user
    console.log(userName) //张三
    console.log(userAge) //18
    const user = {
      name: '张三',
      age: 18
    }
    const {name: userName,age: userAge} = user
    console.log(userName) //张三
    console.log(userAge) //18
  • 2.数组对象解构:

    js
    const pig =[
        {
            username:'你好',
            age:6
        }
    ]
    const [{username,age}] = pig
    console.log(username) //你好
    console.log(age) //6
    const pig =[
        {
            username:'你好',
            age:6
        }
    ]
    const [{username,age}] = pig
    console.log(username) //你好
    console.log(age) //6
  • 3.多级对象解构

    js
    const user = {
    name: '张三',
    age: 18,
    sex: '男',
    address: '北京市海淀区',
    hobbies: ['吃饭', '睡觉', '打豆豆'],
    friends: [
        {
          name: '李四',
          age: 18,
          sex: '男',
          address: '北京市海淀区',
          hobbies: []
        },
        {
          name:'王五'
        }
      ]
    }
    const {name,age,hobbies:[hobby1,hobby2],friends:[{name:friendName1},{name:friendName2}]} = user
    console.log(name) //张三
    console.log(age) //18
    console.log(hobby1) //吃饭
    console.log(hobby2) //睡觉
    console.log(friendName1) //李四
    console.log(friendName2) //王五
    const user = {
    name: '张三',
    age: 18,
    sex: '男',
    address: '北京市海淀区',
    hobbies: ['吃饭', '睡觉', '打豆豆'],
    friends: [
        {
          name: '李四',
          age: 18,
          sex: '男',
          address: '北京市海淀区',
          hobbies: []
        },
        {
          name:'王五'
        }
      ]
    }
    const {name,age,hobbies:[hobby1,hobby2],friends:[{name:friendName1},{name:friendName2}]} = user
    console.log(name) //张三
    console.log(age) //18
    console.log(hobby1) //吃饭
    console.log(hobby2) //睡觉
    console.log(friendName1) //李四
    console.log(friendName2) //王五

4.对象

4.1创建对象三种方式

4.1.1字面量创建

js
const user ={}
const user ={}

4.1.2利用new Object()创建

js
const user = new Object()
user.username = '你好'
const user = new Object()
user.username = '你好'
js
const user = new Object({username:'你好'})
const user = new Object({username:'你好'})

4.1.3利用构造函数创建

js
function User(username,age){
    this.username = username
    this.age = age
}
const user = new User('你好')
function User(username,age){
    this.username = username
    this.age = age
}
const user = new User('你好')

4.2构造函数

  • 构造函数:是一种特殊的函数,主要用来初始化对象
  • 使用场景:常规的 {...} 语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数快速创建多个类似的对象

TIP

构造函数在技术上是常规函数。 不过有两个约定

  1. 它们的命名以大写字母开头。
  2. 它们只能由 "new" 操作符来执行。
js
function User(username,age){
    this.username = username
    this.age = age
}
const user = new User('你好')
function User(username,age){
    this.username = username
    this.age = age
}
const user = new User('你好')
  1. 使用 new 关键字调用函数的行为被称为实例化
  2. 实例化构造函数时没有参数时可以省略 ()
  3. 构造函数内部无需写return,返回值即为新创建的对象
  4. 构造函数内部的 return 返回的值无效,所以不要写return
  5. new Object() new Date() 也是实例化构造函数

4.2.1实例化过程

  1. 创建新对象
  2. 构造函数this指向新对象
  3. 执行构造函数代码,修改this,添加新的属性
  4. 返回新对象

4.3实例成员&静态成员

4.3.1实例成员

通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。

说明:

  1. 实例对象的属性和方法即为实例成员
  2. 为构造函数传入参数,动态创建结构相同但值不同的对象
  3. 构造函数创建的实例对象彼此独立互不影响。

4.3.2静态成员

构造函数的属性和方法被称为静态成员 说明:

  1. 构造函数的属性和方法被称为静态成员
  2. 一般公共特征的属性或方法静态成员设置为静态成员
  3. 静态成员方法中的 this 指向构造函数本身
js
function User(username,age){
  this.username = username
  this.age = age
}
User.sayHello = function(){
  console.log('hello')
}
const user = new User('张三',18);
console.log(user.username)
console.log(user.age)
User.sayHello
function User(username,age){
  this.username = username
  this.age = age
}
User.sayHello = function(){
  console.log('hello')
}
const user = new User('张三',18);
console.log(user.username)
console.log(user.age)
User.sayHello

5.内置构造函数

5.1Object

Object 对象中的常用方法包括:

1. Object.keys() 返回一个由对象所有可枚举属性组成的数组。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
console.log(keys); // 输出: ["a", "b", "c"]
const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
console.log(keys); // 输出: ["a", "b", "c"]

2. Object.values() 返回一个由对象所有可枚举属性对应的值组成的数组。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
const values = Object.values(obj);
console.log(values); // 输出: [1, 2, 3]
const obj = { a: 1, b: 2, c: 3 };
const values = Object.values(obj);
console.log(values); // 输出: [1, 2, 3]

3. Object.entries() 返回一个由对象所有可枚举属性及其对应的值组成的二维数组。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
const entries = Object.entries(obj);
console.log(entries); // 输出: [["a", 1], ["b", 2], ["c", 3]]
const obj = { a: 1, b: 2, c: 3 };
const entries = Object.entries(obj);
console.log(entries); // 输出: [["a", 1], ["b", 2], ["c", 3]]

4. Object.getOwnPropertyNames() 返回一个由对象自身的可枚举属性组成的数组。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
const names = Object.getOwnPropertyNames(obj);
console.log(names); // 输出: ["a", "b", "c"]
const obj = { a: 1, b: 2, c: 3 };
const names = Object.getOwnPropertyNames(obj);
console.log(names); // 输出: ["a", "b", "c"]

5. Object.getOwnPropertyValues() 返回一个由对象自身的可枚举属性对应的值组成的数组。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
const values = Object.getOwnPropertyValues(obj);
console.log(values); // 输出: [1, 2, 3]
const obj = { a: 1, b: 2, c: 3 };
const values = Object.getOwnPropertyValues(obj);
console.log(values); // 输出: [1, 2, 3]

6. Object.getPrototypeOf() 返回对象的原型(原型链上的原型)。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
const prototype = Object.getPrototypeOf(obj);
console.log(prototype); // 输出: Object {}
const obj = { a: 1, b: 2, c: 3 };
const prototype = Object.getPrototypeOf(obj);
console.log(prototype); // 输出: Object {}

7. Object.create() 创建一个新的对象,该对象的原型为指定的原型(原型链上的原型)。

示例代码:

javascript
const prototype = { a: 1, b: 2, c: 3 };
const obj = Object.create(prototype);
console.log(obj); // 输出: { a: 1, b: 2, c: 3 }
const prototype = { a: 1, b: 2, c: 3 };
const obj = Object.create(prototype);
console.log(obj); // 输出: { a: 1, b: 2, c: 3 }

8. Object.defineProperty() 定义对象的属性,并设置该属性的访问器、修改器和可枚举性。

示例代码:

javascript
const obj = {};
Object.defineProperty(obj, "name", {
 value: "张三",
 writable: true,
 enumerable: true,
 configurable: true
});
console.log(obj.name); // 输出: "张三"
const obj = {};
Object.defineProperty(obj, "name", {
 value: "张三",
 writable: true,
 enumerable: true,
 configurable: true
});
console.log(obj.name); // 输出: "张三"

9. Object.defineProperties() 定义对象的多个属性,并设置这些属性的访问器、修改器和可枚举性。

示例代码:

javascript
const obj = {};
Object.defineProperties(obj, {
 name: {
   value: "张三",
   writable: true,
   enumerable: true,
   configurable: true
 },
 age: {
   value: 25,
   writable: false,
   enumerable: true,
   configurable: false
 }
});
console.log(obj.name); // 输出: "张三"
console.log(obj.age); // 输出: 25
const obj = {};
Object.defineProperties(obj, {
 name: {
   value: "张三",
   writable: true,
   enumerable: true,
   configurable: true
 },
 age: {
   value: 25,
   writable: false,
   enumerable: true,
   configurable: false
 }
});
console.log(obj.name); // 输出: "张三"
console.log(obj.age); // 输出: 25

10. Object.propertyIsEnumerable() 检查对象是否支持指定的属性,并且该属性是可枚举的。

示例代码:

javascript
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.propertyIsEnumerable(obj, "a")); // 输出: true
console.log(Object.propertyIsEnumerable(obj, "b")); // 输出
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.propertyIsEnumerable(obj, "a")); // 输出: true
console.log(Object.propertyIsEnumerable(obj, "b")); // 输出

11.Object.assign() 将所有可枚举属性的值从一个或多个源对象复制到目标对象。 复制对象 示例代码:

javascript
const obj1 = {};
const obj2 = { a: 4, b: 5, d: 6 };
Object.assign(obj1, obj2, {name:'xixi'});
console.log(obj1);
const obj1 = {};
const obj2 = { a: 4, b: 5, d: 6 };
Object.assign(obj1, obj2, {name:'xixi'});
console.log(obj1);

5.2Array

常见方法:

方法作用说明
push添加数组向数组的末尾添加一个或多个元素
pop删除数组删除数组的最后一个元素
unshift添加数组向数组的开头添加一个或多个元素
shift删除数组删除数组的第一个元素
reverse反转数组将数组元素反转
splice删除数组删除数组元素
join连接数组将数组元素连接成字符串
fill填充数组将数组元素填充为指定值
find查找数组查找元素,返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回undefined
sort排序数组对数组进行排序
reverse反转数组将数组元素反转
some判断数组判断数组中是否存在满足指定条件的元素,满足则返回true,否则false
every判断数组判断数组中所有元素是否都满足指定条件,如果所有元素都通过检测返回true,否则返回false
forEach遍历数组不返回数组,经常用于查找遍历数组元素
map迭代数组返回新数组,返回的是处理之后的数组元素,想要使用返回的新数组
filter过滤数组返回新数组,返回的是筛选满足条件的数组元素
reduce累计返回累计处理的结果,经常用于求和
concat合并数组将数组元素合并成一个新数组
slice切片数组返回一个从指定位置开始到结束位置(不包括结束位置)的新数组
includes查找数组判断数组是否包含指定元素
copyWithin复制数组将数组元素从指定位置开始复制到另一个位置
entries迭代数组返回一个由数组元素的键值对组成的迭代器
indexOf查找数组返回指定元素在数组中的位置
lastIndexOf查找数组返回指定元素在数组中的位置

1.reduce() reduce() 方法对数组中的所有元素执行一个由提供的reducer函数(升序执行),将其结果汇总为单个返回值。 ruduce返回累计处理的结果,经常用于求和

基本语法:

js
arr.reduce(function(){},起始值)
arr.reduce(function(){},起始值)
js
arr.reduce(function(上一次值,当前值){},初始值)
arr.reduce(function(上一次值,当前值){},初始值)

累计值参数:

  1. 如果有起始值,则以起始值为准开始累计, 累计值 = 起始值
  2. 如果没有起始值, 则累计值以数组的第一个数组元素作为起始值开始累计
  3. 后面每次遍历就会用后面的数组元素 累计到 累计值 里面 (类似求和里面的 sum )

示例代码: 无初始值

javascript
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce(function(prev,current){
  return prev + current
})
console.log(sum) //156
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce(function(prev,current){
  return prev + current
})
console.log(sum) //156

有初始值

javascript
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce(function(prev,current){
  return prev + current
},10)
console.log(sum) //25
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce(function(prev,current){
  return prev + current
},10)
console.log(sum) //25

2.join() join() 方法用于将数组(或类数组对象)中的所有元素放入一个字符串。

示例代码:

javascript
const arr = [1, 2, 3, 4, 5];
const str = arr.join("-");
console.log(str); //1-2-3-4-5
const arr = [1, 2, 3, 4, 5];
const str = arr.join("-");
console.log(str); //1-2-3-4-5

5.2.1真伪数组转换-Array.from()

静态方法Array.from()

代码示例

js
// 1. 数组转换
const lis = document.querSelectorAll('ul li')
//将伪数组转换为一个真数组,返回的是一个数组
const liss = Array.from(lis);
//现在才能调用pop方法,其它的方法类似
liss.pop()
console.log(liss); // [1, 2, 3, 4, 5]
// 1. 数组转换
const lis = document.querSelectorAll('ul li')
//将伪数组转换为一个真数组,返回的是一个数组
const liss = Array.from(lis);
//现在才能调用pop方法,其它的方法类似
liss.pop()
console.log(liss); // [1, 2, 3, 4, 5]

5.3String

方法作用说明
toUpperCase转换为大写将字符串中的所有字母转换为大写
toLowerCase转换为小写将字符串中的所有字母转换为小写
substring提取子字符串返回一个新字符串,包含从指定位置开始到指定结束位置(不包括结束位置)的子字符串
indexOf查找字符串返回指定字符串在字符串中首次出现的位置,如果未找到则返回-1
lastIndexOf查找字符串返回指定字符串在字符串中最后一次出现的位置,如果未找到则返回-1
replace替换字符串返回一个新字符串,将所有匹配的字符串替换为指定的替换字符串
split分割字符串返回一个新数组,使用指定的分隔符将字符串分割成子字符串数组
charAt获取字符返回指定位置的字符
slice提取子字符串返回一个新字符串,包含从指定位置开始到指定结束位置(不包括结束位置)的子字符串
concat连接字符串将一个或多个字符串连接到当前字符串的末尾,返回新字符串
startsWith检查字符串是否以指定字符串开头检查字符串是否以指定字符串开头,返回布尔值
endsWith检查字符串是否以指定字符串结尾检查字符串是否以指定字符串结尾,返回布尔值
toLocaleString转换为本地字符串将字符串转换为本地字符串,例如根据语言环境显示日期和时间
trim去除字符串两端的空格返回一个新字符串,去除字符串两端的空格
trimStart去除字符串左端的空格返回一个新字符串,去除字符串左端的空格
trimEnd去除字符串右端的空格返回一个新字符串,去除字符串右端的空格
match匹配字符串检查字符串是否匹配指定的正则表达式,返回布尔值
search检查字符串是否匹配指定的正则表达式检查字符串是否匹配指定的正则表达式,返回第一个匹配的结果
repeat重复字符串返回一个新字符串,将字符串重复指定的次数
includes检查字符串是否包含指定字符串检查字符串是否包含指定字符串,返回布尔值

5.4Number

5.4.1toFixed() 设置保留小数位的长度

js
//数值类型
const price = 12.5678
//保留两位小数,四舍五入
console.log (price.toFixed(2)) //12.57
//保留三位小数,四舍五入
console.log (price.toFixed(3)) //12.568
//保留四位小数,四舍五入
console.log (price.toFixed(4)) //12.5678
//数值类型
const price = 12.5678
//保留两位小数,四舍五入
console.log (price.toFixed(2)) //12.57
//保留三位小数,四舍五入
console.log (price.toFixed(3)) //12.568
//保留四位小数,四舍五入
console.log (price.toFixed(4)) //12.5678

6.编程思想

6.1面向过程

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次 调用就可以了。
  • 面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。

6.2面向对象

  • 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
  • 面向对象是以对象功能来划分问题,而不是步骤。

  • 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
  • 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
  • 面向对象的特性:
    • 封装
    • 继承
    • 多态

6.3对比

---优点缺点
面向过程性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程没有面向对象易维护、易复用、易扩展
面向对象易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护性能比面向过程低

8.原型prototype

8.1原型定义

  • 构造函数通过原型分配的函数是所有对象所 共享的
  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
  • 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
  • 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
  • 构造函数和原型对象中的this 都指向 实例化的对象

代码示例: 这里的sayName方法是所有实例共享的,所以我们可以把它定义在原型对象上,这样所有实例就可以共享这个方法。

js
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 定义一个方法
Person.prototype.sayName = function () {
  console.log(this.name);
}
const p1 = new Person('张三', 18);
p1.sayName();
const p2 = new Person('李四', 19);
p2.sayName();
console.log(p1.sayName === p2.sayName);
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 定义一个方法
Person.prototype.sayName = function () {
  console.log(this.name);
}
const p1 = new Person('张三', 18);
p1.sayName();
const p2 = new Person('李四', 19);
p2.sayName();
console.log(p1.sayName === p2.sayName);

TIP

公共的属性写到构造函数里面

公共的方法写道原型对象中

构造函数和原型对象中的this 都指向 实例化的对象

另一种定义原型方法的形式:

将需要共享的方法放入代码块中

js
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype={
  //重新指回创造这个原型对象的构造函数
  constructor:Person,
  sing:()=>{
    console.log("唱歌");
  },
  dance:()=>{
    console.log("跳舞");
  }
}
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype={
  //重新指回创造这个原型对象的构造函数
  constructor:Person,
  sing:()=>{
    console.log("唱歌");
  },
  dance:()=>{
    console.log("跳舞");
  }
}

给Array数组原型拓展方法

js
Array.prototype.sum = function(){
  return this.reduce((a, b) => a + b, 0)
}
const arr = [1,2,3,4,5,6]
console.log(arr.sum());
Array.prototype.sum = function(){
  return this.reduce((a, b) => a + b, 0)
}
const arr = [1,2,3,4,5,6]
console.log(arr.sum());

8.2constructor属性

每个原型对象里面都有个constructor 属性(constructor 构造函数)

作用: 该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸

js
function Person(name, age) {
  this.name = name;
  this.age = age;
}
console.log(Person.prototype.constructor === Person)
function Person(name, age) {
  this.name = name;
  this.age = age;
}
console.log(Person.prototype.constructor === Person)
js
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype={
  //重新指回创造这个原型对象的构造函数
  constructor:Person,
  sing:()=>{
    console.log("唱歌");
  },
  dance:()=>{
    console.log("跳舞");
  }
}
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype={
  //重新指回创造这个原型对象的构造函数
  constructor:Person,
  sing:()=>{
    console.log("唱歌");
  },
  dance:()=>{
    console.log("跳舞");
  }
}

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数

8.3对象原型__proto__

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype

原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。

WARNING

  • __proto__ 是JS非标准属性
  • [[prototype]]和__proto__意义相同
  • 用来表明当前实例对象指向哪个原型对象prototype
  • __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数

8.4原型继承

继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性

继承过程

js
//构造需要继承的类
function Person() {
  this.eye = 2,
  this.hands= 2,
  this.legs= 2
}
//Man类
function Man() {
}
//Woman类
function Woman() {
  this.name=name
}

//将Man和Woman的原型指向Person
Man.prototype = new Person()
Woman.prototype = new Person()

//将Man和Woman的构造函数指向它们自己
Man.prototype.constructor = Man
Woman.prototype.constructor = Woman

//实例化Man和Woman
const man = new Man()
const woman = new Woman()
    
//打印man和woman
console.log(man)
console.log(man.eye)
console.log(woman)
console.log(woman.eye)
//构造需要继承的类
function Person() {
  this.eye = 2,
  this.hands= 2,
  this.legs= 2
}
//Man类
function Man() {
}
//Woman类
function Woman() {
  this.name=name
}

//将Man和Woman的原型指向Person
Man.prototype = new Person()
Woman.prototype = new Person()

//将Man和Woman的构造函数指向它们自己
Man.prototype.constructor = Man
Woman.prototype.constructor = Woman

//实例化Man和Woman
const man = new Man()
const woman = new Woman()
    
//打印man和woman
console.log(man)
console.log(man.eye)
console.log(woman)
console.log(woman.eye)

8.5原型链

原型链是一种规则

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构

我们将原型对象的链状结构关系称为原型链

image-20231203194015895

8.5.1原型链-查找规则

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象
  3. 如果还没有就查找原型对象的原型(Object的原型对象
  4. 依此类推一直找到 Object 为止(null
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
  6. 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

9.技巧

9.1深浅拷贝

9.1.1浅拷贝

首先浅拷贝和深拷贝只针对引用类型

浅拷贝:拷贝的是地址

常见方法:

  1. 拷贝对象:Object.assgin()

    js
    const o={}
    const obj = {
      name: '张三',
      age: 18,
      arr: [1, 2, 3]
    }
    Object.assign(o, obj)
    const o={}
    const obj = {
      name: '张三',
      age: 18,
      arr: [1, 2, 3]
    }
    Object.assign(o, obj)
  2. 展开运算符 {...obj} 拷贝对象

    js
    const obj = {
     name: '张三',
     age: 18,
     arr: [1, 2, 3]
    }
    const o = {...obj}
    console.log(o)
    o.age = 20
    console.log(o)
    console.log(obj)
    const obj = {
     name: '张三',
     age: 18,
     arr: [1, 2, 3]
    }
    const o = {...obj}
    console.log(o)
    o.age = 20
    console.log(o)
    console.log(obj)
  3. 拷贝数组:Array.prototype.concat() 或者 [...arr]

    js
    const arr = [1, 2, 3]
    const o = arr.concat()
    o[0] = 10
    console.log(o)
    const arr = [1, 2, 3]
    const o = arr.concat()
    o[0] = 10
    console.log(o)

浅拷贝的缺点

  1. 直接赋值和浅拷贝有什么区别?
    • 直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对 象栈里面的地址
    • 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会 相互影响
  2. 浅拷贝怎么理解?
    • 拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
    • 如果属性值是引用数据类型则拷贝的是地址

9.1.2深拷贝

首先浅拷贝和深拷贝只针对引用类型 深拷贝拷贝的是对象,不是地址 常见方法:

  1. 通过递归实现深拷贝
  2. lodash/cloneDeep
  3. 通过JSON.stringify()实现
js
const ob = JSON.parse(JSON.stringify(obj))
console.log(ob)
const ob = JSON.parse(JSON.stringify(obj))
console.log(ob)

9.2异常处理

9.2.1throw抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行

总结:

  1. throw 抛出异常信息,程序也会终止执行
  2. throw 后面跟的是错误提示信息
  3. Error 对象配合 throw 使用,能够设置更详细的错误信息
js
//写一个抛异常代码示例
function test() {
  throw new Error('出错了')
}
test()
//写一个抛异常代码示例
function test() {
  throw new Error('出错了')
}
test()

9.2.2try...catch捕获异常

我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后 总结:

  1. try...catch 用于捕获错误信息
  2. 将预估可能发生错误的代码写在 try 代码段中
  3. 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
  4. finally 不管是否有错误,都会执行

DANGER

finally 不管是否有错误,都会执行

js
function divide(x, y) {
 if (y === 0) {
   throw new Error('除数不能为0');
 }
 return x / y;
}
try {
 console.log(divide(10, 0));
} catch (error) {
 console.error('捕获到异常:', error.message);
}
function divide(x, y) {
 if (y === 0) {
   throw new Error('除数不能为0');
 }
 return x / y;
}
try {
 console.log(divide(10, 0));
} catch (error) {
 console.error('捕获到异常:', error.message);
}

9.3处理this

this 是 JavaScript 最具“魅惑”的知识点,不同的应用场合 this 的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。

目标: 了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法

9.3.1普通函数中的this指向

普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】

普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined

TIP

开启严格模式

js
'use strict'
'use strict'

9.3.2箭头函数中的this指向

箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this!

  1. 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
  2. 箭头函数中的this引用的就是最近作用域中的this
  3. 向外层作用域中,一层一层查找this,直到有this的定义

WARNING

对象没有this

注意情况:

  • [使用箭头函数前需要考虑函数中 this 的值],事件回调函数使用箭头函数时,this 为全局的 window 因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数

  • 同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数

总结:

  1. 箭头函数内不存在this,沿用上一级的
  2. 不适用:
    • 构造函数,原型函数,dom事件函数等等
  3. 适用:
    • 需要使用上层this的地方
  4. 使用正确的话,它会在很多地方带来方便

9.3.3改变this

JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向

  • call()
  • apply()
  • bind()
call()

使用 call 方法调用函数,同时指定被调用函数中 this 的值

  • 语法:

    js
    fun.call(thisArg, arg1, arg2, ...)
    fun.call(thisArg, arg1, arg2, ...)
  • thisArg:在 fun 函数运行时指定的 this 值

  • arg1arg2:传递的其他参数

  • 返回值就是函数的返回值,因为它就是调用函数

代码示例:

js
const obj ={
  uname:'pink'
}
function fn(x,y){
  console.log(this) //window
  console.log(x+y)
}
//改变调用,改变this指向
//这里的1,2对应x,y
fn.call(obj,1,2)
const obj ={
  uname:'pink'
}
function fn(x,y){
  console.log(this) //window
  console.log(x+y)
}
//改变调用,改变this指向
//这里的1,2对应x,y
fn.call(obj,1,2)
apply()

使用 apply 方法调用函数,同时指定被调用函数中 this 的值

  • 语法:

    js
    fun.apply(thisArg, [argsArray])
    fun.apply(thisArg, [argsArray])
    • thisArg:在fun函数运行时指定的 this 值
    • argsArray传递的值,必须包含在数组里面
    • 返回值就是函数的返回值,因为它就是调用函数
    • 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值

代码示例:

js
const obj ={
  uname:'pink'
}
function fn(x,y){
  console.log(this) //window
  console.log(x+y)
}
//改变调用,改变this指向
//这里的1,2对应x,y
fn.apply(obj,[1,2])

//另一种用法
const arr =[100,44,77]
const max = Math.max.apply(null,arr)
console.log(max)
const obj ={
  uname:'pink'
}
function fn(x,y){
  console.log(this) //window
  console.log(x+y)
}
//改变调用,改变this指向
//这里的1,2对应x,y
fn.apply(obj,[1,2])

//另一种用法
const arr =[100,44,77]
const max = Math.max.apply(null,arr)
console.log(max)

TIP

call和apply的区别是

  • 都是调用函数,都能改变this指向
  • 参数不一样,apply传递的必须是数组
bind()
  • bind() 方法不会调用函数。但是能改变函数内部this 指向

  • 语法:

    js
    fun.bind(thisArg, arg1, arg2, ...)
    fun.bind(thisArg, arg1, arg2, ...)
    • thisArg:在 fun 函数运行时指定的 this 值
    • arg1,arg2:传递的其他参数
    • 返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)
    • 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向.

代码示例:

js
const obj ={
  uname:'pink'
}
function fn(x,y){
  console.log(this) //window
  console.log(x+y)
}
//改变调用,改变this指向
//这里的1,2对应x,y
const newFn = fn.bind(obj,1,2)
newFn()
const obj ={
  uname:'pink'
}
function fn(x,y){
  console.log(this) //window
  console.log(x+y)
}
//改变调用,改变this指向
//这里的1,2对应x,y
const newFn = fn.bind(obj,1,2)
newFn()

区别

  • 相同点:
    • 都可以改变函数内部的this指向.
  • 区别点:
    • call 和 apply 会调用函数, 并且改变函数内部this指向.
    • call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
    • bind 不会调用函数, 可以改变函数内部this指向.
  • 主要应用场景:
    • call 调用函数并且可以传递参数
    • apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
    • bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.

9.4性能优化

9.4.1防抖debounce

单位时间内,频繁触发事件,只执行最后一次 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间 简单来说:就是只要被打断了,就要重新来

代码示例: 手写防抖

js
const box = document.querySelector('.box')
let i =1
function mouseMove(){
  console.log(i++)
}
function debounce(fn,t){
  let timer
  return function(){
    if(timer) clearTimeout(timer)
    timer = setTimeout(()=>{
      fn() //调用fn
    },t)
  }
}
box.addEventListener('click',debounce(mouseMove,500))
const box = document.querySelector('.box')
let i =1
function mouseMove(){
  console.log(i++)
}
function debounce(fn,t){
  let timer
  return function(){
    if(timer) clearTimeout(timer)
    timer = setTimeout(()=>{
      fn() //调用fn
    },t)
  }
}
box.addEventListener('click',debounce(mouseMove,500))

9.4.2节流 throttle

单位事件内,频繁触发事件,只执行一次

使用场景:

  • 高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等

代码示例: 利用Lodash库实现节流 -500ms后采取+

js
const box = document.querySelector('.box')
let i =1
function mouseMove(){
  console.log(i++)
}

box.addEventListener('click',_.throttle(mouseMove,500))
const box = document.querySelector('.box')
let i =1
function mouseMove(){
  console.log(i++)
}

box.addEventListener('click',_.throttle(mouseMove,500))

手写节流示例:

js
const box = document.querySelector('.box')
let i =1
function mouseMove(){
  console.log(i++)
}
function debounce(fn,t){
  let timer = null
  return function(){
    if(!timer) {
      timer = setTimeout(()=>{
      fn() //调用fn
      //清空定时器
      timer=null
    },t)

    }
  }
}
box.addEventListener('click',debounce(mouseMove,500))
const box = document.querySelector('.box')
let i =1
function mouseMove(){
  console.log(i++)
}
function debounce(fn,t){
  let timer = null
  return function(){
    if(!timer) {
      timer = setTimeout(()=>{
      fn() //调用fn
      //清空定时器
      timer=null
    },t)

    }
  }
}
box.addEventListener('click',debounce(mouseMove,500))

WARNING

setTimeout中是无法删除定时器的,因为定时器还在运作

所以使用timer = null而不是clearTimeout(timer)

防抖节流总结

性能优化说明使用场景
防抖单位时间内,频繁触发事件,只执行最后一次搜索框输入、手机号、邮箱验证输入检测
节流单位时间内,频繁触发事件,只执行一次鼠标移动、页面尺寸缩放、滚动条滚动等