Skip to content

JS 进阶

作用域分为局部作用域全局作用域

  • 函数作用域、块级作用域是局部作用域
  • script标签、js文件是全局作用域

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

作用域链的查找规则:优先查找当前函数作用域,查找不到则依次逐级查找父级作用域直到全局作用域

  • 引用计数法:被引用的次数,存在嵌套引用问题
  • 标记清除法:从根部扫描对象,能查找到的就是使用的,查找不到的就要回收

闭包 = 内层函数 + 外层函数的变量

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

闭包可能引起内存泄漏

变量提升:用var声明变量会有变量提升,允许变量在声明之前被访问

不建议用var声明变量

变量提升的流程:

  1. 先把var变量提升到当前作用域的最前面
  2. 只提升变量声明,不提升变量赋值
  3. 然后依次执行代码

函数提升类似于变量提升,函数表达式不存在提升

  • 动态参数:在函数内部用arguments,伪数组
  • 剩余参数:函数最后一个形参前加...,获取多余的实参,真数组
(x, y) => x + y

返回对象字面量时,需要加括号

用于批量赋值

const [max, min, avg] = arr

数组开头时,要加分号

;[b, a] = [a, b]

变量名要与属性名一致

可以重新改名,新变量名写在冒号后面

函数参数也可以用对象解构

const { uname: username, age } = { uname: '喜多', age: 16 }

用于遍历数组,索引号是可选的

被遍历的数组.forEach(function (element, index) {
// 函数体
})

用于筛选数组符合条件的元素,返回新数组

被遍历的数组.filter(function (element, index) {
return 筛选条件
})
  1. 用对象字面量创建
  2. new Object()创建
  3. 用构造函数创建

构造函数首字母大写

function Pig(name) {
this.name = name
}
const p = new Pig('佩奇')

实例化:使用new关键字调用函数

实例化没有参数时可以省略括号

构造函数内部不用写return

实例化执行过程:

  1. 创建新对象
  2. 构造函数this指向新对象
  3. 执行构造函数代码,修改this,添加新的属性
  4. 返回新对象
  • 实例成员:实例对象的属性和方法,当前实例对象使用
  • 静态成员:构造函数的属性和方法,只能构造函数访问
  1. Object.keys() 获取所有的属性名
  2. Object.values() 获取所有的属性值
  3. Object.assign() 对象拷贝
  1. forEach() 遍历数组,不返回数组
  2. filter() 过滤数组,返回数组
  3. map() 迭代数组,返回数组
  4. reduce() 累计器,返回累计处理的结果
  5. join() 数组元素拼接为字符串
  6. find() 查找元素
  7. every() 检测数组所有元素是否满足条件
  8. Array.from() 伪数组转真数组
  1. length属性 获取字符串长度
  2. split() 将字符串拆分成数组,与join()相反
  3. substring() 字符串截取
  4. startsWith() 检测字符串开头
  5. includes() 判断一个字符串是否包含另一个字符串

面向对象的特性:封装性、继承性、多态性

JS面向对象通过构造函数实现封装

构造函数存在浪费内存的问题

每个构造函数都有一个prototype属性,指向一个对象,也称为原型对象

可以把那些不变的方法,直接定义在prototype对象上,实现方法共享

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

每个原型对象都有一个constructor属性,指向该原型对象的构造函数

给原型对象用对象形式赋值时,可以添加一个constructor属性指向原来的构造函数

对象都会有一个__proto__指向构造函数的prototype原型对象

__proto__是非标准的,且已被弃用,访问对象原型的标准方法是Object.getPrototypeOf()

[[Prototype]]__proto__等价

JS通过原型对象实现继承:

子类.prototype = new 父类()
子类.prototype.constructor = 子类

原型链查找规则:

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

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

浅拷贝:拷贝的是地址

常见方法:

  1. 拷贝对象:Object.assign()或展开运算符{...obj}
  2. 拷贝数组:Array.prototype.concat()[...arr]

深拷贝:拷贝的是对象

常用方法:

  1. 递归函数
  2. js库lodash的_.cloneDeep()
  3. JSON.stringify()

通过递归函数实现深拷贝:

  1. 需要用函数递归
  2. 基本类型直接赋值,数组用递归
  3. 对象用递归
  4. 先数组后对象
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
newObj[k] = oldObj[k]
}
}
}
  1. throw 抛异常,与Error对象配合使用
  2. try...catch...finally 捕获异常,当try代码段中出现错误后,会执行catch代码段,finally代码段不管是否有错误都会执行
  3. debugger 调试代码

普通函数this指向:谁调用我,我就指向谁
箭头函数this指向:函数内不存在this,沿用上一级的

  1. call() 调用函数
  2. apply() 调用函数,必须以数组形式传递
  3. bind() 不调用函数,返回函数

防抖:单位时间内,频繁触发事件,只执行最后一次

防抖的使用场景:搜索框搜索输入,手机号、邮箱验证输入检测

核心思路:

  1. 声明一个定时器变量
  2. 当鼠标每次滑动都先判断是否有定时器了,如果有定时器先清除以前的定时器
  3. 如果没有定时器则开启定时器,记得存到变量里面
  4. 在定时器里面调用要执行的函数
function debounce(fn, t) {
let timer
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(fn, t)
}
}

节流:单位时间内,频繁触发事件,只执行一次

节流的使用场景:高频事件,如鼠标移动、页面尺寸缩放、滚动条滚动等

核心思路:

  1. 声明一个定时器变量
  2. 当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器
  3. 如果没有定时器则开启定时器,记得存到变量里面
  4. 定时器里面调用执行的函数,定时器里面要把定时器清空
function throttle(fn, t) {
let timer = null
return function () {
if (!timer) {
timer = setTimeout(function () {
fn()
timer = null
}, t)
}
}
}