【JS】JS进阶--编程思想、面向对象构造函数、原型、深浅拷贝、异常处理、this处理、防抖节流

文章目录

    • 一、编程思想
      • [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 运算符)
    • 四、深浅拷贝--只针对引用类型
    • 五、异常处理
      • [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)
  • 以函数为中心组织代码
  • 数据与操作分离

示例:制作蛋炒饭的过程

  1. 准备食材(鸡蛋、米饭、油、盐)
  2. 打散鸡蛋
  3. 热锅下油
  4. 炒鸡蛋
  5. 加入米饭
  6. 调味翻炒
  7. 出锅装盘

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 属性,指向一个原型对象。所有实例共享原型对象上的属性和方法。

  1. 公共的属性写到构造函数里面
  2. 公共的方法写到原型对象身上,避免内存浪费
  3. 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. 原型链查找规则

当访问对象属性/方法时:

  1. 先在对象自身查找
  2. 找不到则沿着 __proto__ 到原型对象查找
  3. 继续沿着原型链向上查找,直到 Object.prototype
  4. 如果到 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. 异常处理机制

总结:

  1. throw 抛出异常信息,程序也会终止执行

  2. throw 后面跟的是错误提示信息

  3. Error 对象配合 throw 使用,能够设置更详细的错误信息
    总结:

  4. try...catch 用于捕获错误信息

  5. 将预估可能发生错误的代码写在 try 代码段中

  6. 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息(message

  7. 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 代码至关重要:

  1. 面向对象思想帮助组织复杂代码结构
  2. 原型与继承是 JavaScript 独特而强大的特性
  3. 深浅拷贝正确处理对象复制,避免意外修改
  4. 异常处理提升代码健壮性
  5. this 机制是理解 JavaScript 执行上下文的关键
  6. 防抖节流优化高频事件性能
相关推荐
Clarence Liu3 小时前
golang 剖析 sync包
开发语言·golang
柒儿吖3 小时前
Perl在鸿蒙PC上的使用方法
开发语言·harmonyos·perl
福大大架构师每日一题3 小时前
rust 1.92.0 更新详解:语言特性增强、编译器优化与全新稳定API
java·javascript·rust
m5655bj3 小时前
使用 C# 设置 Word 段落对齐样式
开发语言·c#·word
Можно3 小时前
ES6扩展运算符:从基础到实战的全方位解析
前端·javascript
福尔摩斯张3 小时前
基于TCP的FTP文件传输系统设计与实现(超详细)
linux·开发语言·网络·网络协议·tcp/ip·udp
豆苗学前端3 小时前
闭包、现代JS架构的基石(吊打面试官)
前端·javascript·面试
技术净胜3 小时前
MATLAB 环境搭建与认知实战教程:从下载安装到入门全解析教程
开发语言·matlab
雯0609~3 小时前
uni-app:防止重复提交
前端·javascript·uni-app