初级Java后端之自学前端JS进阶

39 阅读10分钟

JS进阶

Part1、


1.闭包

简单理解:闭包 = 内层函数 + 外层函数的变量

作用:封闭数据,实现数据私有,外部也可以访问函数内部的变量

问题:可能导致内存泄漏

image-20231029192238073

2.变量提升

JS允许变量先被访问后被声明,但只有var声明可以这样(变量被先访问时,值为undefined),let和const不行(它们会报语法错误),且只能出现在同一作用域。实际开发不推荐。

image-20231029192809196

3.函数提升

函数声明前即可调用,也是同一作用域,但函数表达式不存在这种情况

image-20231029193614779

4.函数传参(参数个数不定)

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

image-20231029193746241

(2)剩余参数:是个真数组,用于获取多余的实参,语法符号“ ... ”,开发推荐

image-20231029194046347

5.展开运算符

展开运算符(…),将一个数组进行展开

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

6.箭头函数


6.1 语法


(1)替换函数表达式中匿名函数

image-20231029195127555

(2)只有一个参数可以省略小括号

image-20231029195301802

(3)如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值

image-20231029195541093

(4)加括号的函数体返回对象字面量表达式

image-20231029195635138


6.2 注意


(1)箭头函数没有arguments参数,只有剩余参数

(2)箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this

7.解构赋值


7.1 数组解构


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

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

注意:

在以数组开头的,特别是前面有语句的要加分号,立即执行函数前或后也要加分号;

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

const [a,b,c] = [1,2]
console.log(b) // 2
console.log(c) // undefined

变量的数量小于单元值数量时,剩余参数... 获取剩余单元值,但只能置于最末位

const [a, ...others] = [1,2,2,3,3]
console.log(a) // 1
console.log(others) // [2,2,3,3]

进阶用法

(1)防止有undefined传递单元值的情况,可以设置默认值:

image-20231029203248877

(2)按需导入,忽略某些返回值

image-20231029203311588

(3)支持多维数组的结构

image-20231029203401354


7.2 对象解构


(1)将对象属性和方法快速批量赋值给一系列变量

const pig = { name: '佩奇',age: 6 }
const {name,age}=pig
console.log(name,age) // 佩奇 6

(2)修改对象属性名,将对象属性和方法快速批量赋值给一系列变量

image-20231029204028080

(3)数组对象解构

image-20231029205245511

(4)多级对象解构

image-20231029205415036

8. filter方法

image-20231029213517054

image-20231029213510818

currentValue 必须写, index 可选

Part2、


1.创建对象之构造函数

创建对象的三种方式:

  • 直接创建

image-20231031215850270

  • new 一个Object对象

image-20231031215915021

  • 构造函数:规范【它们的命名以大写字母开头,只能由 "new" 操作符来执行,不用写return】

image-20231031215940403

2.实例成员&静态成员

实例成员就是this相关的属性和方法

image-20231031220815938

静态成员是构造函数的属性和方法

image-20231031220845288

3.内置构造函数

含有构造函数的数据类型(和Java有点像)

  • 引用类型:Object,Array,RegExp,Date 等
  • 包装类型:String,Number,Boolean 等

3.1


Object

三个静态方法

(1)Object.keys 静态方法获取对象中所有属性(键),返回的是一个数组

(2)Object.values 静态方法获取对象中所有属性值,返回的是一个数组

(3)Object. assign 静态方法常用于对象拷贝,或给对象添加属性

image-20231031221905422

image-20231031221936045

3.2


Array

image-20231031222233262

reduce

image-20231031222353314

Part3、


原型

1 概述

  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
  • 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
  • 构造函数和原型对象中的this 都指向 实例化的对象
  • 找到实例对象(若一个实例对象为obj)的原型对象的方法:
    • 推荐:Object.getPrototypeOf(obj)
    • 不推荐:obj.__proto__

image-20231101213858919

2 constructor 属性

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

作用:该属性指向该原型对象的构造函数

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.

function People (name){
    this.name = name
}
//原型对象的构造函数指向People
console.log(People.prototype.constructor)
///原型对象的构造函数指向People
People.prototype.run = function () {
        console.log('跑')
    }
console.log(People.prototype.constructor)
//原型对象的构造函数指向Object
People.prototype = {
        eat: function () { console.log('吃饭') },
        sleep: function () { console.log('睡觉') }
    }
console.log(People.prototype.constructor)  
//将原型对象的构造函数指向People
People.prototype = {
    constructor: People,
    eat: function () { console.log('吃饭') },
    sleep: function () { console.log('睡觉') }
}
console.log(People.prototype.constructor) // 指向People

可以看出来当对象形式赋值,constructor属性指定为构造函数,原型对象的constructor属性才会指向构造函数

3 对象原型

实例对象都会有一个属性_proto_ 指向构造函数的 prototype 原型对象,之所以我们实例对象可以使用构造函数 prototype原型对象的属性和方法,就是因为对象有 _proto_ 原型的存在。

注意:

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

image-20231101220513555

个人理解:

构造函数的prototype指向原型对象

实例对象继承构造函数属性和方法,实例对象的__proto__属性指向原型对象,由此继承原型对象的方法

原型对象的constructor属性指向构造函数(若用对象函数方式给prototype赋值,constructor属性得指定为构造函数)

实例对象的constructor属性指向构造函数

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

4. 原型继承

function People(){
    this.name = name
    this.eat = function(){}
}
function Man() {}
Man.prototype = People
//注意让原型里面的constructor重新指回Man---------这种写法固定
Man.prototype.constructor = Man
//Man添加smoking方法
Man.prototype.smoking=function(){
    console.log("抽烟")
}
function Woman(){
    this.baby = function(){}
}
//Woman此时也继承People,但也有了smoking方法,我们并不想这样
Woman.prototype = People
//注意让原型里面的constructor重新指回Woman-----------固定写法
Woman.prototype.constructor = Woman
new Woman().smoking()

原因:Man 和 Woman 的prototype对象都指向同一个对象People的地址

解决:分别构造People对象

Man.prototype = new People()
Woman.prototype = new People() // Woman没有smoking方法了

5. 原型链

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

image-20231102201908329

function Star(){}
const ldh = new Star()
// ldh对象的__protype__属性指向Star()的prototype对象,Star()的prototype对象的constructor属性指向Star()构造函数
//Star()的prototype对象的__proto__属性指向Object的prototype对象,其prototype的constructor属性指向Object()构造函数
//Object的prototype对象的_proto__属性指向null

原型链-查找规则:

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

Part4、


1. 深浅拷贝


1.1 浅拷贝

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

  • 浅拷贝:拷贝的是地址

  • API :

    • 拷贝数组(使用同拷贝对象API):

      • Array.prototype.concat()
      • [...arr]
    • 拷贝对象:

      • Object.assgin()

      • 展开运算符 {...obj}

      • const obj = { name:'green'}
        const o1 = {}
        Object.assign(o1,obj) //o1拷贝obj
        const o2 = {...obj}	//o2拷贝obj
        
  • 问题:拷贝的是单层对象,没问题,如果有多层就有问题,多层意味着引用数据类型,引用数据类型拷贝的是地址

        let obj1 = {
          name: 'zs',
          age: 20,
          height: 180,
          info: {
            sex: '男',
            weight: 70
          }
        }
    
        let obj2 = {} // 写了这一行,已经表示 obj2 和 obj1 是两个不同的对象了
        // 循环遍历 obj1,循环一次,取obj1里面的一个属性,然后给obj2加上
        for (let key in obj1) {
          obj2[key] = obj1[key]
        }
        // 尝试修改其中一个对象
        obj1.age = 10000
        // 尝试修改对象的一个引用类型的属性
        obj1.info.sex = '女'
    
    	//由下图看出引用数据类型info里面的属性两个对象都改了,而简单数据类型age并未修改
        console.log(obj1)
        console.log(obj2)
    

image-20231102215840385

1.1 深拷贝

(1)深拷贝:拷贝的是对象,不是地址

(2)常用方法

  • 递归实现深拷贝

    function deepCopy(newObj,oldObj){
        for(let k in oldObj){
            if (oldObj[k] instanceof Object){
                newObj[k] = []
                deepCopy(newObj[k], oldObj[k]) 
            } else if (oldObj[k] instanceof Array){
                newObj[k] = {}
                deepCopy(newObj[k], oldObj[k]) 
            } else {
                newObj[k] = oldObj[k]
            }
        }
    }
    
  • lodash/cloneDeep

  • image-20231102222819887

  • 通过JSON.stringify()实现

image-20231102222804966

2. 异常处理


  • throw 抛异常:

    throw new Error('参数不能为空')
    
  • try / catch 捕获异常

  • debugger 在代码中写debugger关键字,浏览器则会开启debug断点模式

  • image-20231102223830091

3. 处理this


3.1 this指向

  • 普通函数this指向:谁调用 this 的值指向谁普通函数严格模式下指向:undefined

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

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

image-20231103195300180

适用:需要使用上层this的地方

不适用:构造函数,原型函数,dom事件函数等等

image-20231103195444760

image-20231103195506517

3.2 改变this

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

​ 使用:函数.call(thisArg, arg1, arg2, ...) thisArg:在 fun 函数运行时指定的 this 值,arg1,arg2:传递的其他参数

image-20231103195837133

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

使用:函数.apply(thisArg, [argsArray]) argsArray:传递的值,必须包含在数组里面

image-20231103200114957

3.bind()-重点 bind() 方法不会调用函数。但是能改变函数内部this 指向

使用:函数.bind(thisArg, arg1, arg2, ...)

  • 返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)

  • 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的

    this指向.

4. 性能优化


4.1 节流 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数

开发使用场景:

假如一张轮播图完成切换需要300ms, 不加节流效果,快速点击,则嗖嗖嗖的切换

加上节流效果, 不管快速点击多少次, 300ms时间内,只能切换一张图片。

4.2 防抖 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

开发使用场景- 搜索框防抖:

假设输入就可以发送请求,但是不能每次输入都去发送请求,输入比较快发送请求会比较多我们设定一个时间,假如300ms, 当输入第一个字符时候,300ms后发送请求,但是在200ms的时候又输入了一个字符,则需要再等300ms 后发送请求