JavaScript 原型链实战:对象继承、Unicode 映射与模块边界
本文目录
对象读取属性时,先检查自身属性,再沿 [[Prototype]] 链向上查找,直到找到属性或到达 null。prototype 是构造函数用于设置实例原型的属性;实例的内部原型关系不是普通的可枚举业务字段。开发时优先使用 Object.create、class 和组合,把 proto 留作历史知识,不用它构建新架构。
用映射表做文本转换
旧练习用“字符=替换字符”构造字典,再逐位替换。这个思路适合有限映射,但要避免继承属性误命中;也要认识到字符串索引按 UTF-16 单元遍历,无法完整处理某些 Unicode 字符。下面示例处理的是明确的单字符词表,并用 Map 保留原样输入。
const substitutions = new Map([
['你', '伱'],
['说', '詤'],
['我', '莪'],
]);
function transformKnownCharacters(input) {
return Array.from(input, (character) => substitutions.get(character) ?? character).join('');
}
transformKnownCharacters('你好吗'); // 伱好吗
若要对 emoji、组合音标或复杂文字做用户可见的字符操作,应按字素簇分段;若要按语言规则转换简繁体,应使用专门语言库,而不是维护几个字的手写替换表。
原型继承与组合
实例自身没有属性时,读取会沿原型链查找;继承关系共享的是原型上的方法,不会自动复制父构造函数的实例字段。调用父构造函数初始化字段与设置原型链是两件事。旧式组合继承会多次执行父构造函数,现代 class 语法可以让意图更清楚,但继承层级仍应保持浅而稳定。
const animal = {
speak() {
return this.name + ' 发出声音';
},
};
function createPet(name) {
return Object.assign(Object.create(animal), { name });
}
const pet = createPet('小猫');
pet.speak(); // 小猫 发出声音
Object.getPrototypeOf(pet) === animal; // true
对象组合往往比深层继承更容易改动。配置和数据对象可用对象展开或 Object.assign 做浅拷贝,但嵌套对象仍共享引用;拷贝方法时也不要把原型成员误认为实例自有字段。检查自己的字段用 Object.hasOwn,兼容旧环境可使用 Object.prototype.hasOwnProperty.call。
不要把状态挂到 window
把局部变量写入 window 会制造全局可变状态,容易发生命名冲突、测试顺序依赖和跨页面污染。浏览器模块使用 import/export 管理依赖;经典脚本若必须暴露接口,应使用单一、明确命名的命名空间,并限制对外 API。服务端 Node 模块也应通过模块导出传递依赖,而不是模拟浏览器全局。
可用 Object.getPrototypeOf 检查真实原型。不要用 for...in 复制任意对象:它会枚举可枚举的自有和继承属性,可能把污染属性也复制进目标。处理不可信对象时限制键集合、目标结构和嵌套深度。
维护检查
- 共享方法是否放在原型或模块中,实例状态是否仍然独立?
- 原型链是否存在意外循环或过深查找?
- 用户可控键名是否可能覆盖 proto、constructor 等敏感字段?
- 遍历时是否只读取自有属性并拒绝不需要的键?
- 全局状态是否可以改为显式参数、模块或依赖注入?
参考:MDN Inheritance and the prototype chain、MDN Object.create、MDN Object.hasOwn、MDN Intl.Segmenter、OWASP Prototype Pollution Prevention。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。