JavaScript 之 进阶
文章目录
- JavaScript 进阶
- 1. 作用域
- 局部作用域
- 函数作用域
- 块作用域
- 全局作用域
- 作用域链
- 垃圾回收机制
- 引用计数法
- 标记清除法
- 闭包
- 变量提升
- 2. 函数进阶
- 函数提升
- 函数参数
- 动态参数
- 剩余参数
- 展开运算符
- 箭头函数
- 箭头函数语法
- 箭头函数参数
- 箭头函数 this
- 3. 解构赋值
- 数组解构
- 对象解构
- 4. 深入对象
- 创建对象
- 构造函数
- 实例成员
- 静态成员
- 5. 内置构造函数
- 引用类型
- Object 类型
- Array类型
- 包装类型
- String类型
- Number类型
- 6. 编程思想
- 面向过程编程
- 面向对象编程
- 7. 构造函数
- 8. 原型
- 原型
- constructor属性
- 对象原型
- 原型继承
- 原型链
- 9. 深浅拷贝
- 浅拷贝
- 深拷贝
- 10. 异常处理
- throw抛异常
- try/catch捕获异常
- debugger
- 11. 处理this
- this指向
- 改变this
- **call改变this指向**
- **apply改变this指向**
- **bind改变this指向**
- 三者的总结
- 12. 性能优化
- 防抖
- 节流
JavaScript 进阶
1. 作用域
- 作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问。
- 作用域分为:
- 局部作用域
- 全局作用域
局部作用域
- 局部作用域分为
函数作用域和块作用域。
函数作用域
- 在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
<script>// 声明 counter 函数function counter(x, y) {// 函数内部声明的变量const s = x + yconsole.log(s) // 18}// 设用 counter 函数counter(10, 8)// 访问变量 sconsole.log(s)// 报错
script>
块作用域
- 在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
let声明的变量会产生块作用域,var不会产生块作用域const声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let或const - 在开发中
let和const经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用const声明成常量。
全局作用域
标签和.js文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。- 全局作用域中声明的变量,任何其它作用域都可以被访问。
<script>// 全局变量 nameconst name = '小明'// 函数作用域中访问全局function sayHi() {// 此处为局部console.log('你好' + name)}// 全局变量 flag 和 xconst flag = truelet x = 10// 块作用域中访问全局if(flag) {let y = 5console.log(x + y) // x 是全局的}
script>
作用域链
- 作用域链本质上是底层的变量查找机制。
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
<script>// 全局作用域let a = 1let b = 2// 局部作用域function f() {let c// let a = 10;console.log(a) // 1 或 10console.log(d) // 报错// 局部作用域function g() {let d = 'yo'// let b = 20;console.log(b) // 2 或 20}// 调用 g 函数g()}console.log(c) // 报错console.log(d) // 报错f();
script>
垃圾回收机制
-
垃圾回收机制(Garbage Collection) 简称
GC -
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
-
不再用到的内存,没有及时释放,就叫做
内存泄漏 -
内存的生命周期:
- 内存分配、内存使用、内存回收
- 全局变量一般不会回收;
- 一般情况下局部变量的值, 不用了, 会被自动回收掉
-
浏览器垃圾回收算法:
- 引用计数法
- 标记清除法
引用计数法
- 引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
- 算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 –
- 如果引用次数是0 ,则释放内存
- 引用计数法存在一个缺陷,当遇到
嵌套引用(循环引用)时,会造成内存泄露。
标记清除法
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
闭包
- 概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。
- 简单理解: 闭包 = 内层函数 + 外层函数的变量。
- 闭包作用:
- 封闭数据,提供操作,让外部也可以访问函数内部的变量。
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
- 闭包可能造成内存泄漏
DOCTYPE html>
<html lang="zh_CN">
<head><meta charset="UTF-8"><title>闭包title>
head><body><script>// 闭包// 简单的写法function outer() {let a = 10function fn() { console.log(a)}fn()}outer()// 常见的闭包形式 外部可以访问使用 函数内部的变量function out() { let n = 100function fun() { console.log(n)}return fun}// out() === fun === function fun() {}// const func = function fun() {}const func = out()func() // 调用函数// 闭包的应用 统计函数调用的次数// 1.定义全局变量,但有可能被改变let i = 0function fn() {i++console.log(`函数被调用了${i}次`)}// 2.定义局部变量 用闭包形式在外部调用,但可能会造成内存泄漏function count() { let i = 0function fn() { i++console.log(`函数被调用了${i}次`)}return fn}const fun = count()script>
body>html>
变量提升
- 变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined let声明的变量不存在变量提升,推荐使用let- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>变量提升title>
head><body><script>// 1.把所有的 var 声明的变量提升到当前作用域的最前面// 2.只提升声明,不提升赋值console.log(num + '件')var num = 10function fn() {console.log(count)var count = 100}fn()script>
body>html>
2. 函数进阶
函数提升
- 函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
- 函数提升能够使函数的声明调用更灵活。
- 函数表达式不存在提升的现象。
- 函数提升出现在相同作用域当中。
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>函数提升title>
head><body><script>// 1.把所有的函数声明的变量提升到当前作用域的最前面// 2.只提升函数声明,不提升函数调用fn()function fn() {console.log('函数提升')}// 函数表达式必须先声明和赋值,后调用 否则报错fun()var fun = function () {console.log('我是函数表达式')}script>
body>html>
函数参数
动态参数
- 动态参数:
arguments是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参 arguments是一个伪数组,只存在于函数中
arguments的作用是动态获取函数的实参- 可以通过for循环依次得到传递过来的实参
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>函数动态参数title>
head><body><script>// 函数动态参数function getSum() {// arguments 动态参数,只存在于函数里面// 是一个伪数组let sum = 0for (let i = 0; i < arguments.length; i++) {sum += arguments[i];}console.log(sum);}getSum(1, 2, 3, 4)getSum(1, 2, 3, 4, 5, 6)script>
body>html>
剩余参数
- 剩余参数:
...允许我们将一个不定数量的参数表示为一个数组...是语法符号,置于最末函数形参之前,用于获取多余的实参- 借助
...获取的剩余实参,是个真数组
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>函数剩余参数title>
head><body><script>// 函数剩余参数function getSum(...arr) {console.log(arr)}getSum()getSum(1, 2)getSum(1, 2, 3)function getSum2(a, b, ...arr) {console.log(arr) //使用的时候不想需要写}getSum2(1, 2, 3)getSum2(1, 2, 3, 4)getSum2(1, 2, 3, 4, 5)script>
body>html>
展开运算符
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>展开运算符title>
head><body><script>const arr1 = [1, 2, 3]//展开运算符可以展开数组console.log(...arr1)// 1. 求数组最大值console.log(Math.max(...arr1))console.log(Math.min(...arr1))// 2. 合并数组const arr2 = [4, 5, 6]const arr = [...arr1, ...arr2]console.log(arr)script>
body>html>
箭头函数
- 箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
() - 箭头函数函数体只有一行代码时可以省略花括号
{},并自动做为返回值被返回
箭头函数语法
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>箭头函数的基本语法title>
head><body><script>// const fn = function () {// console.log(11)// }// 1.箭头函数的基本语法// const fn = () => {// console.log(123)// }// fn()// const fn = (x) => {// console.log(x)// }// fn(123)// 2.只有一个形参的时候,可以省略小括号// const fn = x => {// console.log(x)// }// fn(123)// 3.只有一行代码的时候,可以省略大括号// const fn = x => console.log(x)// fn(123)// 4.只有一行代码的时候,可以省略return// const fn = x => x + x// console.log(fn(123))// 5.箭头函数可以直接返回一个对象const fn = uname => ({uname:uname})console.log(fn('刘德华'))script>
body>html>
箭头函数参数
- 箭头函数中没有
arguments,只能使用...动态获取实参
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>箭头函数的参数title>
head><body><script>// 1.利用箭头函数求和const getSum = (...arr) => {let sum = 0for (let i = 0; i < arr.length; i++) {sum += arr[i]}return sum}const result = getSum(2, 3, 4)console.log(result)script>
body>html>
箭头函数 this
- 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>箭头函数的thistitle>
head><body><script>// 1. 以前this的指向:谁调用的这个函数,this就指向谁console.log(this) // this指向window// 普通函数function fn() {console.log(this) // this指向window}window.fn()// 对象方法里的thisconst obj = {name: 'andy',sayHi: function () {console.log(this) // this指向obj}}obj.sayHi()// 2. 箭头函数的this 指向上一层作用域的this// 函数里的thisconst fun = () => {console.log(this) // this指向window}fun()// 对象方法里的箭头函数thisconst obj2 = {uname: 'pink老师',sayHi: () => {console.log(this) // this指向window}}obj2.sayHi()const obj3 = {uname: 'pink大老师',sayHi: function() { let i = 10const count = ()=>{console.log(this) // this指向obi3}count()}}obj3.sayHi()script>
body>html>
3. 解构赋值
- 解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
数组解构
- 数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
- 赋值运算符
=左侧的[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined - 变量的数量小于单元值数量时,可以通过
...获取剩余单元值,但只能置于最末位 - 允许初始化变量的默认值,且只有单元值为
undefined时默认值才会生效
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>数组解构title>
head><body><script>// const arr = [100, 60, 80]// 数组解构 赋值// const [max, min, avg] = arrconst [max, min, avg] = [100, 60, 80]console.log(max)console.log(min)console.log(avg)// 交换两个变量的值let a = 1let b = 2; //此处必须加 ; 号[b, a] = [a, b]console.log(a, b)script>
body>html>
对象解构
- 对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
- 赋值运算符
=左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象属性的值将被赋值给与属性名相同的变量
- 对象中找不到与变量名一致的属性时变量值为
undefined - 允许初始化变量的默认值,属性不存在或单元值为
undefined时默认值才会生效
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>对象解构title>
head><body><script>// 对象解构的语法// const { uname, age } = { uname: 'pink老师', age: 18 }// 等价于 const uname = obj.uname// 要求属性名和变量名必须一致才可以// console.log(uname)// console.log(age)// 1.对象解构的变量名 可以重新改名 旧变量名:新变量名// uname = 'red老师'const { uname: username, age: Age } = { uname: 'pink老师', age: 18 }console.log(username)console.log(Age)// 2.解构数组对象const pig = [{uname: '佩奇',age: 6}]const [{ uname, age }] = pigconsole.log(uname)console.log(age)script>
body>html>
4. 深入对象
创建对象
- 使用对象字面量创建对象
const o = {name : '迪迦奥特曼'
}
- 使用new Object 创建对象
const a = new Object({ name : '赛罗奥特曼'})
console.log(a)
- 利用构造函数创建对象
构造函数
-
构造函数 :是一种特殊的函数,主要用来初始化对象
-
作用:常规的 {…} 语法允许创建一个对象,此时可以通过构造函数来快速创建多个类似的对象。
-
使用构造函数时的约定:
- 它们的命名以大写字母开头。
- 它们只能由 “new” 操作符来执行。
-
说明:
- 使用
new关键字调用函数的行为被称为实例化 - 实例化构造函数时没有参数时可以省略
() - 构造函数内部无需写return,返回值即为新创建的对象
- 构造函数内部的 return 返回的值无效,所以不要写return
new Object() new Date()也是实例化构造函数
- 使用
-
构造函数实例化执行过程- 创建新对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新对象
DOCTYPE html>
<html lang="zh_CN">
<head><meta charset="UTF-8"><title>创建对象title>
head><body><script>// 利用对象字面量创建对象const o = {name : '迪迦奥特曼'}// 利用new Object创建对象const a = new Object({ name : '赛罗奥特曼'})console.log(a)// 创建一个猪 构造函数function Pig(uname,age) {this.uname = unamethis.age = age}// console.log(new Pig('佩奇',6))// console.log(new Pig('乔治',3))const p = new Pig('佩奇',6)console.log(p)const p1 = new Pig('乔治',3)console.log(p1)script>
body>html>
实例成员
-
通过构造函数创建的对象称为
实例对象,实例对象中的属性和方法称为实例成员。 -
说明
- 构造函数内部
this实际上就是实例对象,为其动态添加的属性和方法即为实例成员。
- 为构造函数传入参数,动态创建结构相同但值不同的对象。
- 构造函数创建的实例对象彼此独立互不影响。
- 构造函数内部
<script>// 构造函数function Person() {// 构造函数内部的 this 就是实例对象// 实例对象中动态添加属性this.name = '小明'// 实例对象动态添加方法this.sayHi = function () {console.log('大家好~')}}// 实例化,p1 是实例对象// p1 实际就是 构造函数内部的 thisconst p1 = new Person()console.log(p1)console.log(p1.name) // 访问实例属性p1.sayHi() // 调用实例方法
script>
静态成员
- 在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员。
- 静态成员指的是添加到构造函数本身的属性和方法
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的
this指向构造函数本身
<script>// 构造函数function Person(name, age) {// 省略实例成员}// 静态属性Person.eyes = 2Person.arms = 2// 静态方法Person.walk = function () {console.log('^_^人都会走路...')// this 指向 Personconsole.log(this.eyes)}
script>
5. 内置构造函数
引用类型
Object 类型
- Object 是内置的构造函数,用于创建普通对象。
- 推荐使用字面量方式声明对象,而不是 Object 构造函数。
- Object的三个常用静态方法(静态方法就是只有构造函数Object可以调用的)
Object.keys() 静态方法:获取对象中所有属性,括号里是对象名,返回的是数组Object.values() 静态方法:获取对象中所有属性值,括号里是对象名,返回的是数组Object. assign() 静态方法:常用于对象拷贝,经常用于给对象添加属性
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>Object静态方法title>
head><body><script>const o = { uname: 'pink', age: 18 }// 1.获得所有的属性名 Object.keys()console.log(Object.keys(o)) // 返回数组 ['uname','age']// 2.获得所有的属性值console.log(Object.values(o)) // 返回数组 ['pink',18]// 3.对象的拷贝// const oo = {}// Object.assign(oo,o)// console.log(oo)Object.assign(o, { gender: '女' })console.log(o)script>
body>html>
Array类型
-
Array 是内置的构造函数,用于创建数组
-
创建数组建议使用字面量创建,不用 Array构造函数创建
-
Array常用实例化方法
方法 作用 说明 forEach遍历数组 不返回,用于不改变值,经常用于查找打印输出值 filter过滤数组 筛选数组元素,并生成新数组 map迭代数组 返回新数组,新数组里面的元素是处理之后的值,经常用于处理数据 reduce累计器 返回函数累计处理的结果,经常用于求和等 fine查找元素 返回符合查找条件的第一个数组元素值,没有则返回undefined every检测数组 检测数组元素是否都符合指定条件,如果都符合,返回true,否则返回false -
redece()方法:
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>数组reduce方法title>
head><body><script>// 数组reduce方法// arr.reduce(function(上一次值,当前值){},初始值)const arr = [1, 5, 8]// 1.没有初始值const total = arr.reduce(function (prev, current) {return prev + current})console.log(total)// 2.有初始值const total2 = arr.reduce(function (prev, current) {return prev + current}, 10)console.log(total2)// 3.箭头函数的写法const total3 = arr.reduce((prev, current) => prev + current, 20)console.log(total3)script>
body>html>
find()方法:
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>数组find方法title>
head><body><script>const arr = [{name: '小米',price: 1999},{name: '华为',price: 3999}]// 找小米这个对象,并且返回这个对象// const mi = arr.find(function(item){// // console.log(item)// // console.log(item.name)// return item.name === '小米'// })// console.log(mi)// 箭头函数const mi = arr.find( item => item.name === '小米')console.log(mi)script>
body>html>
every()方法
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>数组every方法title>
head><body><script>const arr = [10, 20, 30]// every()方法,检测数组元素是否都符合条件,如果都符合返回true,否则返回falseconst flag = arr.every(item => item >= 10)console.log(flag)// some()方法,检测数组元素是否符合条件,如果有一个条件符合就返回true,否则返回falsescript>
body>html>
Array.from()静态方法:把伪数组转换为真数组
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>数组静态方法title>
head><body><ul><li>li><li>li><li>li>ul><script>// Array.from() 把伪数组转换为真数组const lis = document.querySelectorAll('ul li')console.log(lis) // 得到一个伪数组// lis.pop() // 报错const liss = Array.from(lis)liss.pop()console.log(liss) // 得到一个真数组script>
body>html>
包装类型
- 在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法。
- 之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
<script>// 字符串类型const str = 'hello world!'// 统计字符的长度(字符数量)console.log(str.length)// 数值类型const price = 12.345// 保留两位小数price.toFixed(2) // 12.34
script>
String类型
String是内置的构造函数,用于创建字符串。String的常见实例方法和属性- 实例属性
length用来获取字符串的度长(重点) - 实例方法
split('分隔符')用来将字符串拆分成数组(重点) - 实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点) - 实例方法
startsWith(检测字符串[, 检测位置索引号])检测是否以某字符开头(重点) - 实例方法
includes(搜索的字符串[, 检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点) - 实例方法
toUpperCase用于将字母转换成大写 - 实例方法
toLowerCase用于将就转换成小写 - 实例方法
indexOf检测是否包含某字符 - 实例方法
endsWith检测是否以某字符结尾 - 实例方法
replace用于替换字符串,支持正则匹配 - 实例方法
match用于查找字符串,支持正则匹配
- 实例属性
String也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>String常见方法title>
head><body><script>// 1.split 把 字符串 转换为 数组 和join()相反const str = 'pink,red'const arr = str.split(',')console.log(arr)const str1 = '2022-4-8'const arr1 = str1.split('-')console.log(arr1)// 2.字符串的截取 substring(开始的索引号[,结束的索引号])// 2.1 如果省略结束的索引号,默认截取到最后// 2.2 结束的索引号不包括想要截取的部分const str2 = '现在已经不做核酸了'console.log(str2.substring(6,8))// 3.startsWith 判断是不是以某个字符开头const str3 = 'pink老师上课中'console.log(str.startsWith('pink')) // true// 4.includes 判断某个字符是不是包含在一个字符串里面const str4 = '我是pink老师'console.log(str.includes('pink')) // truescript>
body>html>
Number类型
Number是内置的构造函数,用于创建数值。- 常用方法:
toFixed()设置保留小数位的长度
DOCTYPE html>
<html lang="zh_CN">
<head><meta charset="UTF-8"><title>toFixed方法title>
head><body><script>// toFixed() 方法可以让数字保留指定的小数位数,括号里是需要保留的位数,遵循四舍五入const num = 10.923console.log(num.toFixed(0))console.log(num.toFixed(1))const num1 = 10console.log(num.toFixed(2))script>
body>html>
6. 编程思想
面向过程编程
- 面向过程:面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
- 面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
面向对象编程
- 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
- 面向对象是以对象功能来划分问题,而不是步骤。
- 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
- 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
- 面向对象编程的特点:
封装性继承性多态性
| 面向过程编程 | 面向对象编程 | |
|---|---|---|
| 优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
| 缺点 | 没有面向对象易维护、易复用、易扩展 | 性能比面向过程低 |
7. 构造函数
- 封装是面向对象思想中比较重要的一部分,
js面向对象可以通过构造函数实现的封装。 - 同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
- 构造函数方法很好用,但是存在浪费内存的问题
8. 原型
原型
- 构造函数通过原型分配的函数是所有对象所
共享的。 - JavaScript 规定,
每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象 - 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。构造函数和原型对象中的this 都指向 实例化的对象
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>构造函数和原型对象里的this指向title>
head><body><script>let thatfunction Star(uname) {this.uname = uname}// 原型对象里面的函数 this 指向还是 实例对象 ldhStar.prototype.sing = function() { that = thisconsole.log('唱歌')}// 实例对象 ldh// 构造函数里面的 this 就是 实例对象 ldhconst ldh = new Star('刘德华')ldh.sing()console.log(that === ldh) // truescript>
body>html>
constructor属性
constructor:每个原型对象里面都有个constructor 属性- 作用:
该属性指向该原型对象的构造函数
DOCTYPE html>
<html lang="zh_CN">
<head><meta charset="UTF-8"><title>constructor属性title>
head><body><script>// cunstructor 构造函数function Star() {}// // 这是追加方法// Star.prototype.sing = function() { // console.log('唱歌')// }// Star.prototype.dance = function() { // console.log('跳舞')// }console.log(Star.prototype)// 这是重新赋值Star.prototype = { // 需要重新指回创造这个原型对象的构造函数身上constructor: Star,sing:function() { console.log('唱歌')},dance:function() { console.log('跳舞')}}console.log(Star.prototype)console.log(Star.prototype.constructor)const ldh = new Star()console.log(Star.prototype.constructor === Star)script>
body>html>
对象原型
- 对象都会有一个属性
__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。 __proto__是JS非标准属性[[prototype]]和__proto__意义相同__proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数

原型继承
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>原型继承title>
head><body><script>// 公共的部分放到原型上// const Person = {// eays: 2,// head: 1// }// 构造函数 new 出来的对象 结构一样,但是对象不一样function Person() {this.eyes = 2this.head = 1}// 女人 构造函数 继承Personfunction Woman() { }// Woman 通过原型来继承 Person// 父构造类(父类) 子构造类(子类)// 子类的原型 = new 父类Woman.prototype = new Person()// 指回原来的构造函数 Woman.prototype.constructor = Woman// 给女人添加一个方法 生孩子Woman.prototype.baby = function() { console.log('宝贝')}const red = new Woman()console.log(red)// console.log(Woman.prototype)// 男人 构造函数 继承Personfunction Man() { }// 通过原型来继承 PersonMan.prototype = new Person()// 指回原来的构造函数Man.prototype.constructor = Manconst pink = new Man()console.log(pink)// console.log(Man.prototype)script>
body>html>
原型链
- 基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,
我们将原型对象的链状结构关系称为原型链。

- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是
__proto__指向的 prototype 原型对象) - 如果还没有就查找原型对象的原型(Object的原型对象)
- 依此类推一直找到 Object 为止(null)
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线- 可以使用
instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>原型链title>
head><body><script>// 构造一个Person()function Person() { }// 构造函数实例化const ldh = new Person()// 实例对象的对象原型 === 构造函数的原型对象console.log(ldh.__proto__ === Person.prototype) // true// 实例对象的对象原型的constructor属性 === 构造函数console.log(ldh.__proto__.constructor === Person) // trueconsole.log(Person.prototype.__proto__ === Object.prototype) // trueconsole.log(Object.prototype.__proto__ === null) // true// instanceof 运算符 用来检测构造函数的prototype是否出现在某个实例对象的原型链上console.log(ldh instanceof Person) // trueconsole.log(ldh instanceof Object) // trueconsole.log(ldh instanceof Array) // falseconsole.log([1, 2, 3] instanceof Array) // trueconsole.log(Array instanceof Object) // truescript>
body>html>
9. 深浅拷贝
浅拷贝
- 浅拷贝和深拷贝只针对引用类型,而且拷贝的是地址
- 如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
- 直接赋值和浅拷贝的区别:
- 直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
- 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响
- 常见方法:
- 拷贝对象:
Object.assgin()或者展开运算符 {...obj} - 拷贝数组:
Array.prototype.concat()或者展开运算符[...arr]
- 拷贝对象:
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>浅拷贝title>
head><body><script>// 声明一个对象const pink = {name: 'pink',age: 18,family:{baby:'小pink'}}// 浅拷贝————对象// 方法1 {...对象名}const red = { ...pink }console.log(red) // {name: 'pink', age: 18}red.name = 'red'console.log(red) // {name: 'red', age: 18}// 此时pink不会发生变化console.log(pink) // {name: 'pink', age: 18}// 方法2 assign()const blue = {}Object.assign(blue, pink)console.log(blue) // {name: 'pink', age: 18}blue.name = 'blue'console.log(blue) // {name: 'blue', age: 18} console.log(pink) // {name: 'pink', age: 18}// 浅拷贝的缺点// 只能拷贝一层对象,多的话拷贝不了red.family.name = '小red'console.log(red) // 此时red里的family已经改变了console.log(pink); // 但是pink里的family也随之改变了,这是不应该的script>
body>html>
深拷贝
- 首先浅拷贝和深拷贝只针对引用类型,深拷贝:拷贝的是对象,不是地址
- 深拷贝常见方法:
- 通过
递归实现深拷贝 - 通过
lodash/cloneDeep实现 - 通过
JSON.stringify()实现
- 通过
递归实现深拷贝
- 函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数- 简单理解:函数内部自己调用自己, 这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件
return
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>递归函数深拷贝title>
head><body><script>const obj = {uname: 'pink',age: 18,hobby: ['乒乓球', '羽毛球'],family:{brother: '张三'}}const o = {}const p = {}// 拷贝函数function deepCopy(newObj, oldObj) {for (let k in oldObj) {// 处理数组问题if (oldObj[k] instanceof Array) {// newObj[k] 接收 []newObj[k] = []// oldObj[k] ['乒乓球', '足球']deepCopy(newObj[k], oldObj[k])}// 处理对象的问题else if (oldObj[k] instanceof Object) {newObj[k] = {}deepCopy(newObj[k], oldObj[k])} else {// k——属性名 uname,age oldObj[k]——属性值// newObj[k] === o.uname 给新对象添加属性newObj[k] = oldObj[k]console.log()}}}deepCopy(o, obj)deepCopy(p, obj)console.log(o)console.log(p)p.family.brother = '李四 'o.age = 20o.hobby[0] = '睡觉'console.log(o)console.log(obj)script>
body>html>
lodash/cloneDeep实现深拷贝
- JavaScript工具库lodash里面cloneDeep内部实现了深拷贝
DOCTYPE html>
<html lang="zh_CN">
<head><meta charset="UTF-8"><title>lodash实现深拷贝title>
head><body><script src="./lodash.min.js">script><script>const obj = {uname: 'pink',age: 18,hobby: ['乒乓球', '羽毛球'],family:{brother: '张三'}}const o = _.cloneDeep(obj)console.log(o)o.family.brother = '李四'console.log(obj)script>
body>html>
JSON.stringify()实现深拷贝
DOCTYPE html>
<html lang="zh_CN">
<head><meta charset="UTF-8"><title>JSON实现深拷贝title>
head><body><script>const obj = {uname: 'pink',age: 18,hobby: ['乒乓球', '羽毛球'],family:{brother: '张三'}}console.log(JSON.stringify(obj))const o = JSON.parse(JSON.stringify(obj))o.family.brother = '我不知道'console.log(o)console.log(obj)script>
body>html>
10. 异常处理
throw抛异常
- 异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>throw抛出异常title>
head><body><script>function fn(x, y) {if (!x || !y) {// throw '没有参数传递进来'throw new Error('没有参数传递进来')}return x + y}console.log(fn(1,2))console.log(fn())script>
body>html>
try/catch捕获异常
- 我们可以通过
try / catch捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后- try…catch 用于捕获错误信息
- 将预估可能发生错误的代码写在 try 代码段中
- 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
finally代码部分 不管是否有错误,都会执行
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>try-catch捕获异常title>
head><body><p>123p><script>function fn() {try {// 可能出现错误的代码 写到try里const p = document.querySelector('p')p.style.color = 'red'} catch(err){// 拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行console.log(err.message) // 需要加return 中断程序// return// 或者配合throw使用throw new Error('选择器出现错误!')} finally{// 不管程序对不对,一定会执行的代码alert('弹出对话框')}console.log(11)}fn()script>
body>html>
debugger
- debugger可以在程序中打出断点,运行的时候由程序员决定是否执行下一步
11. 处理this
this指向
- 普通函数的this指向:
- 普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
- 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>普通函数的this指向title>
head><body><button>按钮button>><script>// 普通函数 谁调用this,this就指向谁console.log(this) // this指向windowfunction fn() {console.log(this) // this指向window}window.fn()window.setTimeout(function () {console.log(this) // this指向window}, 1000)document.querySelector('button').addEventListener("click", function () {console.log(this) // this指向button})const obj = {sayHi: function () {console.log(this) // this指向obj}}obj.sayHi()script>
body>html>
- 箭头函数的this指向:
- 箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !
- 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义
- 注意:箭头函数不适用于
构造函数,原型函数,字面量对象中函数,dom事件函数
改变this
call改变this指向
- 使用 call 方法调用函数,同时指定被调用函数中 this 的值
- 语法:
fun.call(thisArg, arg1, arg2, ...)thisArg:在 fun 函数运行时指定的 this 值arg1,arg2:传递的其他参数- 返回值就是函数的返回值,因为它就是调用函数
- 语法:
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>call改变this指向title>
head><body><script>const obj = {uname: 'pink'}function fn(x, y) {console.log(this) // 此时this指向windowconsole.log(x + y)}// 1. 调用函数// 2.改变 this 指向fn.call(obj, 1, 2) // 调用fn()并改变this指向为objscript>
body>html>
apply改变this指向
-
使用 apply 方法调用函数,同时指定被调用函数中 this 的值
-
语法:
fun.apply(thisArg, [argsArray])thisArg:在fun函数运行时指定的 this 值argsArray:传递的值,必须包含在数组里面- 返回值就是函数的返回值,因为它就是调用函数
- 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>apply改变this指向title>
head><body><script>const obj = {age: 18}function fn(x, y) {console.log(this) // this指向 objconsole.log(x + y)}// 1. 调用函数// 2.改变 this 指向// fn.apply(this指向谁, 数组参数) // this的指向必须要写fn.apply(obj, [1, 2]) // 调用fn()并改变this指向为obj// 3.返回值 本身就是再调用函数,所以返回值就是函数的返回值// 使用场景 求数组最大值const arr = [60, 80, 120]const max = Math.max.apply(Math,arr)const min = Math.min.apply(null,arr)console.log(max, min)// 使用场景 求数组最大值console.log(Math.max(...arr))script>
body>html>
bind改变this指向
- bind() 方法不会调用函数。但是能改变函数内部this 指向
- 语法:
fun.bind(thisArg, arg1, arg2, ...)thisArg:在 fun 函数运行时指定的 this 值arg1,arg2:传递的其他参数- 返回由指定的 this 值和初始化参数改造的
原函数拷贝 (新函数) - 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向.
- 语法:
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>bind改变this指向title>
head><body><button>发送短信button><script>const obj = {age: 18}function fn() {console.log(this) // this指向 obj}// 1. bind不会调用函数// 2. 可以改变 this 指向const fun = fn.bind(obj) // 改变this指向为obj// console.log(fun)fun()// 需求,有一个按钮,点击一下就禁用,2秒后开启 // 以下所有的this都指向了buttondocument.querySelector('button').addEventListener('click', function () {this.disabled = truewindow.setTimeout(function () {// 在这个函数里,我们需要this指向由原来的window改为btnthis.disabled = false}.bind(this), 2000)})script>
body>html>
三者的总结
- 相同点:
- 都可以改变函数内部的this指向.
- 区别点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2…形式 apply 必须数组形式[arg]
bind 不会调用函数, 可以改变函数内部this指.
- 主要应用场景:
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向
12. 性能优化
防抖
- 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
- 举一个例子:王者荣耀里面的回城机制就是执行的防抖,只要被打断就要重新开始计算回城时间。
DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>性能优化之防抖title><style>.box {width: 500px;height: 500px;background-color: #ccc;color: #fff;text-align: center;font-size: 100px;}style>
head><body><div class="box">div><script src="./lodash.min.js">script><script>// 利用防抖实现性能优化// 需求:鼠标在盒子上移动,里面的数字就会跟着变化const box = document.querySelector('.box')let i = 1function mouseMove() {box.innerHTML = i++// 如果里面存在大量消耗性能的代码,比如dom操作和数据处理,可能造成卡顿}// 添加事件// box.addEventListener('mousemove',mouseMove)// 一.利用lodash库实现防抖 —— 500毫秒之后采取 +1// 语法:_.debounce(fun,时间)// box.addEventListener('mousemove',_.debounce(mouseMove,500))// 二.手写防抖函数// 核心是利用 setTimeout 定时器来实现// 1.声明定时器变量// 2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除定时器// 3.如果没有定时器,则开启定时器,存入到定时器变量里面// 4.定时器里面写函数声明function debounce(fn, t) {let timer// return 返回一个匿名函数return function() { if (timer) clearTimeout(timer)timer = setTimeout(function() { fn() //加小括号调用fn函数},t)}}box.addEventListener('mousemove',debounce(mouseMove,500))script>
body>html>
节流
- 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
- 举个例子:王者荣耀里的英雄技能释放机制,就是利用了节流,多次点击技能只会释放一次。
DOCTYPE html>
<html lang="zh_CN"><head><meta charset="UTF-8"><title>性能优化之节流title><style>.box {width: 500px;height: 500px;background-color: #ccc;color: #fff;text-align: center;font-size: 100px;}style>
head><body><div class="box">div><script src="./lodash.min.js">script><script>// 利用防抖实现性能优化// 需求:鼠标在盒子上移动,里面的数字就会跟着变化const box = document.querySelector('.box')let i = 1function mouseMove() {box.innerHTML = i++// 如果里面存在大量消耗性能的代码,比如dom操作和数据处理,可能造成卡顿}// 添加事件// box.addEventListener('mousemove',mouseMove)// 一.利用lodash库实现节流 - 500毫秒之后采取 +1 // 语法:_.throttle(fun,时间)// box.addEventListener('mousemove', _.throttle(mouseMove, 500))// 二.手写一个节流函数,每个500ms + 1// 节流的核心就是利用定时器(setTimeout)来实现// 1.声明一个定时器变量// 2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器// 3.如果没有定时器,则开启定时器,存入到定时器变量里面// 3.1 定时器里面调用执行的函数// 3.2 定时器里面要把定时器清空function throttle(fn,t){let timer = nullreturn function() { if (!timer) { timer = setTimeout(function() { fn()// 清空定时器timer = null}, t);}}}box.addEventListener('mousemove',throttle(mouseMove,1000))script>
body>html>
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
