文章目录
- 一、编程思想
- [1. 面向过程编程](#1. 面向过程编程)
- [2. 面向对象编程](#2. 面向对象编程)
- [3. 面向过程 vs 面向对象](#3. 面向过程 vs 面向对象)
- 二、构造函数与原型
- [4. 构造函数实现面向对象](#4. 构造函数实现面向对象)
- [5. 构造函数的内存问题](#5. 构造函数的内存问题)
- 三、原型系统
- [6. 原型对象概念](#6. 原型对象概念)
- [7. 原型中的 this 指向](#7. 原型中的 this 指向)
- [8. 扩展内置对象原型](#8. 扩展内置对象原型)
- [9. constructor 属性](#9. constructor 属性)
- [10. 对象原型 proto](#10. 对象原型 proto)
- [11. 原型继承](#11. 原型继承)
- [12. 原型链查找规则](#12. 原型链查找规则)
- [13. instanceof 运算符](#13. instanceof 运算符)
- 四、深浅拷贝--只针对引用类型
- [14. 浅拷贝](#14. 浅拷贝)
- [15. 深拷贝](#15. 深拷贝)
- 方法一:递归实现
- [方法二:使用 JSON 方法(有局限性)](#方法二:使用 JSON 方法(有局限性))
- 方法三:使用第三方库lodash
- 五、异常处理
- [16. 异常处理机制](#16. 异常处理机制)
- [六、this 指向与处理](#六、this 指向与处理)
- [17. this 指向规则](#17. this 指向规则)
- [普通函数的 this](#普通函数的 this)
- [箭头函数的 this](#箭头函数的 this)
- [18. 改变 this 指向](#18. 改变 this 指向)
- [call() - 立即执行](#call() - 立即执行)
- [apply() - 立即执行(数组参数)](#apply() - 立即执行(数组参数))
- [bind() - 返回新函数](#bind() - 返回新函数)
- 三者区别总结
- 七、防抖与节流
- [19. 防抖(Debounce)](#19. 防抖(Debounce))
- [20. 节流(Throttle)](#20. 节流(Throttle))
- [21. 防抖与节流的区别与应用场景](#21. 防抖与节流的区别与应用场景)
- [22. 相关事件补充](#22. 相关事件补充)
- 总结
一、编程思想
1. 面向过程编程
面向过程是一种传统的编程范式,它将程序视为一系列顺序执行的步骤。开发者需要分析问题,将解决方案分解为一个个具体的步骤,然后用函数实现这些步骤。
特点:
- 关注"如何做"(How to do)
- 以函数为中心组织代码
- 数据与操作分离
示例:制作蛋炒饭的过程
- 准备食材(鸡蛋、米饭、油、盐)
- 打散鸡蛋
- 热锅下油
- 炒鸡蛋
- 加入米饭
- 调味翻炒
- 出锅装盘
2. 面向对象编程
面向对象编程(OOP) 将程序视为一组相互作用的对象,每个对象都是具有特定功能的独立实体。
三大特性:
- 封装性:将数据和方法隐藏在对象内部,只暴露必要的接口
- 继承性:子类可以继承父类的属性和方法,实现代码复用
- 多态性:同一接口可以有不同的实现方式
示例:餐厅系统
- 对象:厨师、服务员、顾客、收银员
- 每个对象有明确的职责:
- 厨师负责烹饪
- 服务员负责点菜上菜
- 收银员负责结账
3. 面向过程 vs 面向对象
| 对比维度 | 面向过程 | 面向对象 |
|---|---|---|
| 核心思想 | 步骤分解 | 对象交互 |
| 代码组织 | 以函数为中心 | 以对象为中心 |
| 数据存储 | 数据与函数分离 | 数据与方法封装在一起 |
| 适用场景 | 简单任务、一次性脚本 | 复杂系统、长期维护项目 |
| 复用性 | 函数复用 | 对象复用、继承复用 |
二、构造函数与原型
4. 构造函数实现面向对象
javascript
// 构造函数(类)
function Person(name, age) {
// 实例属性
this.name = name;
this.age = age;
// 实例方法 - 每个实例都会创建独立的方法副本
this.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
}
// 创建实例
const person1 = new Person('Alice', 25);
const person2 = new Person('Bob', 30);
console.log(person1.name); // Alice
console.log(person2.name); // Bob
5. 构造函数的内存问题
问题 :每个实例都会创建独立的方法副本,造成内存浪费
javascript
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log('Hi, ' + this.name);
};
}
const p1 = new Person('小明');
const p2 = new Person('小红');
console.log(p1.sayHi === p2.sayHi); // false - 两个独立的方法
三、原型系统
6. 原型对象概念
JavaScript 中每个函数都有一个 prototype 属性,指向一个原型对象。所有实例共享原型对象上的属性和方法。
- 公共的属性写到构造函数里面
- 公共的方法写到原型对象身上,避免内存浪费
- JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
javascript
function Star(uname, age) {
this.uname = uname; // 公共属性
this.age = age;
}
// 公共方法写到原型对象上
Star.prototype.sing = function() {
console.log('唱歌');
}
const ldh = new Star('刘德华', 55);
const zxy = new Star('张学友', 58);
// 所有实例共享同一个方法
console.log(ldh.sing === zxy.sing); // true
7. 原型中的 this 指向
构造函数和原型对象中的 this 都指向实例化的对象。
javascript
let that;
function Star(uname) {
that = this; // 构造函数中的this指向实例对象
this.uname = uname;
}
Star.prototype.sing = function() {
that = this; // 原型方法中的this指向实例对象
console.log('唱歌');
}
const ldh = new Star('刘德华');
ldh.sing();
console.log(that === ldh); // true
8. 扩展内置对象原型
javascript
// 为数组添加自定义方法
// 最大值方法
Array.prototype.max = function() {
return Math.max(...this);
}
// 最小值方法
Array.prototype.min = function() {
return Math.min(...this);
}
// 求和方法
Array.prototype.sum = function() {
return this.reduce((prev, item) => prev + item, 0);
}
// 使用示例
console.log([1, 2, 3].max()); // 3
console.log([1, 2, 3].min()); // 1
console.log([1, 2, 3].sum()); // 6
// 注意:扩展内置对象原型需谨慎,可能与其他库冲突
9. constructor 属性
每个原型对象都有一个 constructor 属性,指向其构造函数。当重写原型对象时,需要手动设置constructor。
javascript
function Person(name) {
this.name = name;
}
const p = new Person('Lucy');
console.log(p.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
// 重置原型后需要恢复 constructor
Person.prototype = {
// 需要手动设置 constructor
constructor: Person,
sayHello() {
console.log('Hello');
}
};
10. 对象原型 proto
每个对象都有一个 __proto__ 属性(现代浏览器推荐使用 Object.getPrototypeOf()),指向其构造函数的原型对象。
注意:
- proto 是JS非标准属性
\[prototype\]\]和__proto__意义相同
- __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
javascript
function Star() {}
const ldh = new Star();
// 对象原型指向构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype); // true
// 对象原型的constructor指向构造函数
console.log(ldh.__proto__.constructor === Star); // true
11. 原型继承
通过原型实现对象之间的继承关系。
javascript
// 父构造函数
function Person() {
this.eyes = 2;
this.head = 1;
}
// 子构造函数
function Woman() {
}
// 核心:通过原型继承父构造函数
Woman.prototype = new Person();
// 指回原来的构造函数
Woman.prototype.constructor = Woman;
// 添加子构造函数自己的方法
Woman.prototype.baby = function() {
console.log('baby');
}
const red = new Woman();
console.log(red.eyes); // 2(继承自Person)
red.baby(); // baby(自己的方法)
12. 原型链查找规则
当访问对象属性/方法时:
- 先在对象自身查找
- 找不到则沿着
__proto__到原型对象查找 - 继续沿着原型链向上查找,直到
Object.prototype - 如果到
Object.prototype仍找不到,返回undefined

javascript
function Person() {}
const ldh = new Person();
// 原型链关系
console.log(ldh.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// instanceof 检查原型链
console.log(ldh instanceof Person); // true
console.log(ldh instanceof Object); // true
console.log(Array instanceof Object); // true
13. instanceof 运算符
判断构造函数的 prototype 是否出现在对象的原型链上。
四、深浅拷贝--只针对引用类型
14. 浅拷贝
特点 :只拷贝对象的第一层属性,如果属性值是引用类型,则拷贝的是地址。
javascript
// 浅拷贝方法
const obj = {
name: 'Alice',
hobbies: ['reading', 'music'],
info: { age: 25 }
};
// 1. Object.assign()
const copy1 = Object.assign({}, obj);
// 2. 展开运算符
const copy2 = { ...obj };
// 3. 数组浅拷贝
const arr = [1, 2, { a: 3 }];
const arrCopy1 = arr.slice();
const arrCopy2 = [...arr];
const arrCopy3 = arr.concat();
// 浅拷贝的问题
copy1.hobbies.push('sports');
console.log(obj.hobbies); // ['reading', 'music', 'sports'] - 被修改了!
15. 深拷贝
特点 :完全拷贝对象及其嵌套对象,新旧对象完全独立。
方法一:递归实现
javascript
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];
}
}
}
方法二:使用 JSON 方法(有局限性)
javascript
const obj = {
name: 'Alice',
hobbies: ['reading', 'music'],
date: new Date()
};
const deepCopy = JSON.parse(JSON.stringify(obj));
// 局限性:
// 1. 不能处理函数、undefined、Symbol
// 2. 不能处理循环引用
// 3. Date对象会变成字符串
// 4. RegExp、Error对象会变成空对象
// 5. 会丢失原型链
方法三:使用第三方库lodash
html
<script src="https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js"></script>
<script>
const obj = {
name: 'Alice',
hobbies: ['reading', 'music']
};
const deepCopy = _.cloneDeep(obj);
</script>
五、异常处理
16. 异常处理机制
总结:
throw抛出异常信息,程序也会终止执行
throw后面跟的是错误提示信息
Error对象配合throw使用,能够设置更详细的错误信息
总结:
try...catch用于捕获错误信息将预估可能发生错误的代码写在
try代码段中如果
try代码段中出现错误后,会执行catch代码段,并截获到错误信息(message)
finally不管是否有错误,都会执行
javascript
// 1. throw 抛出异常
function fn(x, y) {
if (!x || !y) {
throw new Error('没有参数传递进来');
}
return x + y;
}
// 2. try...catch 捕获异常
try {
// 可能发生错误的代码
const p = document.querySelector('p');
p.style.color = 'red';
} catch (err) {
// 处理错误
console.log(err.message);
} finally {
// 无论是否出错都会执行的代码
alert('弹出对话框');
}
// 3. debugger 相当于断点调试
六、this 指向与处理
17. this 指向规则
普通函数的 this
javascript
// 规则:谁调用,this 指向谁
// 1. 全局上下文
function globalFunc() {
console.log(this); // 严格模式:undefined,非严格模式:window
}
globalFunc();
// 2. 对象方法
const user = {
name: 'Alice',
sayHi() {
console.log(this.name); // this 指向实例对象
}
};
user.sayHi(); // Alice
// 3. 方法赋值给变量
const sayHi = user.sayHi;
sayHi(); // undefined(this 指向 window 或 undefined)
// 4. 事件处理函数
button.onclick = function() {
console.log(this); // 指向 button 元素
};
箭头函数的 this
javascript
// 规则:继承外层作用域的 this,定义时确定,无法改变
const obj = {
name: 'Alice',
// 普通函数方法
regularFunc: function() {
console.log(this.name); // Alice
const innerArrow = () => {
console.log(this.name); // Alice(继承外层 this)
};
innerArrow();
},
// 箭头函数方法(不推荐)
arrowFunc: () => {
console.log(this); // 指向外层作用域的 this(通常是 window)
}
};
// 不适合使用箭头函数的场景:
// 1. 对象方法(this 指向问题)
// 2. 构造函数(不能作为构造函数)
// 3. 原型方法(this 指向问题)
// 4. 事件处理函数(this 不指向 DOM 元素)
18. 改变 this 指向
call() - 立即执行
javascript
<script>
const obj = {
uname: 'pink'
}
function fn(x, y) {
console.log(this)
console.log(x + y)
}
// fn()
// 1. 调用函数
// 2. 改变this指向
fn.call(obj, 1, 2)
</script>
apply() - 立即执行(数组参数)
javascript
<script>
const obj = {
uname: 'pink',
age: 18
}
function fn(x, y) {
console.log(this)
console.log(x + y)
}
// 1. 调用函数
// 2. 改变this指向
fn.apply(obj, [1, 2])
// 3. 返回值 本身就是在调用函数,所以 返回值就是函数的返回值
// 使用场景 求数组最大值
// const max = Math.max(1, 2, 3)
// console.log(max)
const arr = [1, 2, 3]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(Math, arr)
console.log(max, min)
// console.log(Math.max(arr))
console.log(Math.max(...arr))
</script>
bind() - 返回新函数
javascript
<button>发送短信</button>
<script>
const obj = {
uname: 'pink',
age: 18
}
function fn() {
console.log(this)
}
// 1. bind 不会调用函数
// 2. 能改变this指向
// 3. 返回值是个函数,但这个函数的this是更改过的
const fun = fn.bind(obj)
console.log(fun)
fun()
// 需求:有一个按钮,点击里面就禁用,两秒钟之后开启
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
// 禁用按钮
this.disabled = true
setTimeout(function () {
this.disabled = false
}.bind(this), 2000)
})
</script>
三者区别总结
| 方法 | 调用方式 | 参数传递 | 返回值 | 应用场景 |
|---|---|---|---|---|
call |
立即调用 | 逐个传递 | 函数执行结果 | 借用方法、明确参数个数 |
apply |
立即调用 | 数组传递 | 函数执行结果 | 参数数组、数学计算 |
bind |
不调用,返回新函数 | 可预先传递部分参数 | 新函数(永久绑定this) | 事件处理、函数柯里化 |
七、防抖与节流
19. 防抖(Debounce)
原理:事件触发后等待一段时间再执行,如果在这段时间内再次触发,则重新计时。
javascript
// 使用 lodash
// _.debounce(func, [wait=0], [options])
// 手写防抖函数
function debounce(fn, t) {
let timer;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
fn();
}, t);
}
}
20. 节流(Throttle)
原理:事件触发后立即执行,但在指定时间内只执行一次。
javascript
// 使用 lodash
// _.throttle(func, [wait=0], [options])
// 手写节流函数
function throttle(fn, t) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(function () {
fn();
timer = null;
}, t);
}
}
}
21. 防抖与节流的区别与应用场景
| 特性 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 原理 | 合并多次操作为一次 | 固定时间内只执行一次 |
| 执行时机 | 最后一次触发后等待一段时间执行 | 固定时间间隔执行 |
| 重置时机 | 每次触发都重置计时器 | 时间间隔内只执行一次 |
| 应用场景 | 搜索框输入、窗口调整大小 | 滚动事件、鼠标移动、高频点击 |
22. 相关事件补充
视频播放进度保存: 使用节流实现视频播放进度的定时保存和恢复。
javascript
// 定时保存进度
video.ontimeupdate = _.throttle(() => {
localStorage.setItem('currentTime', video.currentTime);
}, 1000);
// 恢复进度
video.onloadeddata = () => {
video.currentTime = localStorage.getItem('currentTime') || 0;
};
总结
JavaScript 进阶知识涵盖了面向对象编程的核心概念、原型系统、高级函数技巧以及性能优化策略。掌握这些内容对于编写高质量、可维护的 JavaScript 代码至关重要:
- 面向对象思想帮助组织复杂代码结构
- 原型与继承是 JavaScript 独特而强大的特性
- 深浅拷贝正确处理对象复制,避免意外修改
- 异常处理提升代码健壮性
- this 机制是理解 JavaScript 执行上下文的关键
- 防抖节流优化高频事件性能