目录
- 一、数据结构基础:线性与非线性分类
-
- [1.1 线性结构](#1.1 线性结构)
- [1.2 非线性结构](#1.2 非线性结构)
- 二、JS原型式面向对象:不靠Class也能实现面向对象
-
- [2.1 函数两种调用形态,this完全不同](#2.1 函数两种调用形态,this完全不同)
-
- [2.1.1 普通函数直接调用 `fn()`](#2.1.1 普通函数直接调用
fn()) - [2.1.2 new + 构造函数调用 `new Fn()`](#2.1.2 new + 构造函数调用
new Fn())
- [2.1.1 普通函数直接调用 `fn()`](#2.1.1 普通函数直接调用
- [2.2 new关键字完整执行四步](#2.2 new关键字完整执行四步)
-
- [实例属性 vs 原型属性对照表](#实例属性 vs 原型属性对照表)
- [2.3 原型关键属性名词梳理](#2.3 原型关键属性名词梳理)
- 三、JS设计哲学:一切皆对象,原型链查找规则
-
- [3.1 顶层设计:Object是所有对象源头](#3.1 顶层设计:Object是所有对象源头)
- [3.2 原型链查找规则](#3.2 原型链查找规则)
- 四、全文总结
-
- [4.1 核心知识点复盘](#4.1 核心知识点复盘)
- [4.2 常见避坑指南](#4.2 常见避坑指南)
一、数据结构基础:线性与非线性分类
数据结构按照元素关联关系分为线性、非线性两大类,是算法与封装类的底层依托。
1.1 线性结构
元素一一前后对应,连续有序,包含三类:栈、队列、链表
- 栈Stack:FILO ( first in last out )先进后出:后进元素先弹出,类比摞盘子,从顶部存取;调用栈、表达式求值依赖栈。
- 队列Queue:FIFO(first in first out)先进先出:先入队的数据先出队,类比排队结账,事件循环任务队列底层就是队列。
- 链表:节点依靠指针串联,无连续内存。
队列固定4个标准方法:
push(x):元素追加至队列尾部(入队)pop():删除并取出队列头部元素(出队)peek():只读取队首,不移除元素empty():布尔返回,判断队列是否为空
1.2 非线性结构
元素一对多/多对多,无固定前后顺序:
- 树:层级嵌套结构(DOM树、原型链本质树形结构)
- 堆:特殊完全二叉树,用于优先队列、排序场景
二、JS原型式面向对象:不靠Class也能实现面向对象
JS核心特点:函数是一等对象、无传统类,全基于原型实现面向对象。
2.1 函数两种调用形态,this完全不同
2.1.1 普通函数直接调用 fn()
- this:浏览器指向window
- 返回值:由return决定,无return默认undefined
- 只执行函数逻辑,不生成实例
js
function greeting() {
console.log(this); // 普通执行this=全局
}
greeting.a = "1"; // 函数本身也是对象,可以挂载属性
console.log(greeting.a);// 1
greeting();
2.1.2 new + 构造函数调用 new Fn()
- this自动绑定新创建的空实例对象,在构造函数内挂载实例自有属性
- 默认自动返回新实例;若return引用类型(对象/数组),则以return内容作为返回值
- 构造函数自带
prototype原型对象,存放所有实例共享方法
注:构造函数和普通函数用首字母大小写区分(程序员之间的默认规则)
js
// 约定:构造函数首字母大写
function Greeting(name) {
console.log(this); // new后this=实例
this.name = name; // 实例自有属性,把传入的参数给实例
}
// 原型挂载共享方法,所有实例指向一个内存空间
// 其中一个实例改了它,所有实例一起变
Greeting.prototype.say = function () {
console.log(`我叫${this.name},很高兴认识你`);
};
Greeting.prototype.work = function () {
console.log(`我叫${this.name},我正在工作`);
};
const zmt = new Greeting("zmt");// 创造一个实例
zmt.say();// 我叫zmt,我正在工作
2.2 new关键字完整执行四步
- JS在堆中创建一块全新空实例对象
- 将构造函数内部this指向这个新空对象
- 执行构造函数代码,通过
this.属性给实例挂载私有属性 - 构造函数无手动return对象,默认自动返回新建实例;return对象则替换返回值
实例属性 vs 原型属性对照表
| 定义方式 | 存储位置 | 内存特点 | 修改范围 | 定义位置 |
|---|---|---|---|---|
| this.xxx | 单个实例自身 | 每个实例独立开辟内存,多实例多份数据 | 仅修改当前实例 | 构造函数内部 |
| Fn.prototype.xxx | 构造函数原型对象 | 全实例共用同一份内存,节省空间 | 全部实例同步生效 | 构造函数外部 |
js
function Person(name) {
this.name = name; // 实例私有
}
Person.prototype.say = function(){} // 原型共享方法
let p1 = new Person('张三')
let p2 = new Person('李四')
console.log(p1.say === p2.say) // true,共用同一个函数
console.log(p1.name === p2.name) // false,属性相互独立
2.3 原型关键属性名词梳理
构造函数.prototype:函数专属属性,指向原型对象,作为所有实例公共仓库实例.__proto__:每个对象隐式属性,永远指向该实例的构造函数的prototypexxx.constructor:原型自带属性,指向原构造函数
三、JS设计哲学:一切皆对象,原型链查找规则
3.1 顶层设计:Object是所有对象源头
Object是顶层内置函数:
let obj = {}等价于new Object()- Array、Function、Date、RegExp全都是构造函数,继承自Object体系
3.2 原型链查找规则
- 实例读取属性/方法时:优先在自身实例查找,找到直接使用
- 自身无对应属性,顺着
__proto__向上查找原型对象 - 原型没有,继续顺着原型的
__proto__向上,直到Object.prototype Object.prototype.__proto__ = null,原型链查找终点为null,找不到返回undefined
html
<!DOCTYPE html>
<html lang="en">
<body>
<script>
function Person(name,age){
this.name=name;
this.age=age;
}
// 原型挂载公共属性与方法
Person.prototype.poem='仁义礼智信';
Person.prototype.say=function(){
console.log(`我叫${this.name}`);
}
Person.prototype.timeMF=function(){
console.log(`时间管理魔法`);
}
const zs=new Person('zs',18);
console.log(zs);
// zs自身无toString,沿原型链找到Object.prototype.toString
console.log(zs.toString());
</script>
</body>
</html>
原链读取(查值,顺着原型链找)
实例自身没有该属性 → .proto 找原型
javascript
function Person() {}
Person.prototype.name = "张三";
const p = new Person();
// 读取name
console.log(p.name);
// p自身没有name → 去原型找,拿到"张三"
2、赋值(改值,只改实例,不动原型)
javascript
p.name = "李四";
console.log(p.name); // 李四(实例新增name)
console.log(Person.prototype.name); // 张三【原型丝毫没变】
赋值操作规则:
JS 不会跑到原型上修改原型属性;
直接在p 实例身上新建一个自身属性 name;
以后再读 p.name,优先读实例自己的,屏蔽原型同名属性。
总结
读沿原型链向上查,写只落在实例上;
基本类型改不动原型,引用类型方法能改原型里的数据(特殊)。
四、全文总结
4.1 核心知识点复盘
- 数据结构:栈FILO、队列FIFO,双栈可以逻辑模拟队列;线性一对一、非线性一对多。
- JS构造函数:普通调用this指向全局,new调用this指向新建实例,new分四步创建对象。
- 内存优化:实例属性
this.xxx独占内存,原型prototype.xxx全实例共享,开发通用方法统一放原型。 - 原型链:实例自身→实例__proto__(构造原型)→Object.prototype→null,自上而下检索属性。
- JS无原生类,原型是JS面向对象原生实现方案。
4.2 常见避坑指南
- ❌ 避坑1:构造函数中通用方法不要写在
this上,多实例会重复创建函数,内存冗余,统一挂载prototype。 - ❌ 避坑2:修改原型属性,所有已创建实例都会同步变更,原型尽量挂载方法,少存引用类型数据。
- ❌ 避坑3:new构造函数内若return数组/对象,会截断默认实例返回,极易引发bug。
- ❌ 避坑4:原型链查找只做读取,赋值属性优先挂载到实例自身,不会修改原型原有属性。