【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. 防抖节流优化高频事件性能
相关推荐
ganshenml2 分钟前
【Android】 开发四角版本全解析:AS、AGP、Gradle 与 JDK 的配套关系
android·java·开发语言
我命由我123452 分钟前
Kotlin 运算符 - == 运算符与 === 运算符
android·java·开发语言·java-ee·kotlin·android studio·android-studio
少云清4 分钟前
【接口测试】3_Dubbo接口 _Telnet或python远程调用Dubbo接口
开发语言·python·dubbo·接口测试
盒子69107 分钟前
【golang】替换 ioutil.ReadAll 为 io.ReadAll 性能会下降吗
开发语言·后端·golang
alonewolf_9913 分钟前
Java类加载机制深度解析:从双亲委派到热加载实战
java·开发语言
Hi_kenyon14 分钟前
VUE3套用组件库快速开发(以Element Plus为例)三
前端·javascript·vue.js
J总裁的小芒果15 分钟前
原生Table写一行保证两条数据
javascript·vue.js·elementui
jqq66624 分钟前
解析ElementPlus打包源码(五、copyFiles)
前端·javascript·vue.js
无限进步_41 分钟前
【数据结构&C语言】对称二叉树的递归之美:镜像世界的探索
c语言·开发语言·数据结构·c++·算法·github·visual studio
CSDN_RTKLIB1 小时前
C++取模与取余
开发语言·c++