js原型有哪些规则

来源:我的博客作者:郭世昌头衔:网络博主
导读:本期聚焦于郭世昌创作的《js原型有哪些规则》,敬请观看详情。理解JavaScript的原型机制是掌握这门语言的关键一步。原型规则决定了对象属性的查找顺序、this的指向行为以及继承的实现方式,却常常被死记硬背式的学习方式搞得云里雾里。本文从最基础的规则讲起,逐条拆解所有引用类型的构造函数默认prototype属性、对象的隐式原型指向、构造函数与实例的关系,以及原型链的查找过程和继承的实际应用。文中配有完整的代码示例和内存关系分析,帮助你彻底理清__proto__与prototype的区别,学会用原型链解决属性共享与继承问题,避免开发中常见的原型污染和覆盖陷阱。

JavaScript没有传统面向对象语言中类的概念(ES6的class只是语法糖),它的继承和对象间的关系全部依赖原型来构建。很多面试题、很多诡异的bug,追根溯源都出在对原型规则理解不透彻上。本文把js原型的核心规则整理成几条,配合代码逐条验证,读完之后你对__proto__、prototype、原型链这些概念会有一个清晰的整体认知。

js原型有哪些规则

规则一:所有引用类型都有一个隐式原型__proto__

第一条规则也是最基本的规则:所有的引用类型(数组、函数、对象)都具备对象特性,可以自由扩展属性,同时它们都有一个__proto__属性(隐式原型),这个属性的值是一个对象或者null。在浏览器环境中,__proto__是一个非标准属性,但现代浏览器和Node.js都支持它,ES6提供了Object.getPrototypeOfObject.setPrototypeOf作为标准替代方案。

来看一段验证代码:

var obj = {};
obj.name = 'hello';          // 自由扩展属性
console.log(obj.__proto__ === Object.prototype);  // true

var arr = [];
console.log(arr.__proto__ === Array.prototype);   // true

function fn() {}
console.log(fn.__proto__ === Function.prototype); // true

可以看出,普通对象的隐式原型指向Object.prototype,数组的隐式原型指向Array.prototype,函数的隐式原型指向Function.prototype。这条规则揭示了对象与构造函数之间的天然联系:实例通过__proto__与构造函数的prototype建立关联。

需要注意的一点是,__proto__只存在于引用类型上。原始值如数字、字符串、布尔值本身没有属性,我们调用'abc'.length之所以能工作,是因为JavaScript临时包装了一个对象,属性访问结束后该对象就被销毁了,这一点不要混淆。

规则二:函数都有prototype属性,且默认带有constructor

所有的函数都有一个prototype属性(显式原型),这个属性的值默认是一个对象,该对象默认只有一个不可枚举的constructor属性,指向这个函数本身。这条规则与规则一是配套的:prototype是给将来new出来的实例准备的原型模板。

function Foo(name) {
    this.name = name;
}
console.log(typeof Foo.prototype);              // object
console.log(Foo.prototype.constructor === Foo); // true

// 扩展原型上的方法,所有实例共享
Foo.prototype.sayName = function () {
    console.log('my name is ' + this.name);
};

var f1 = new Foo('张三');
var f2 = new Foo('李四');
f1.sayName();  // my name is 张三
f2.sayName();  // my name is 李四
console.log(f1.sayName === f2.sayName);  // true,共享同一个函数

上面的例子演示了原型最经典的用法:把方法定义在prototype上,所有实例共享同一个函数引用,既节省内存,又体现了面向对象中方法共享的设计思想。如果把方法写在构造函数内部通过this赋值,每个实例都会拷贝一份,造成不必要的内存开销。

还有一个容易踩的坑:如果直接重写整个prototype对象,例如Foo.prototype = {...},新对象的constructor就不再指向原来的Foo了,需要手动补上Foo.prototype.constructor = Foo,否则instanceof判断虽然正常,但constructor检查会出问题。许多类库和框架源码中都保留了这一步修正,可见其重要性。

规则三:实例的__proto__指向构造函数的prototype

当函数被作为构造函数使用(通过new调用)时,实例的__proto__会指向该构造函数的prototype。这是整个原型体系的连接枢纽,也是原型链能够形成的根本原因。用伪代码描述new的过程大致是:创建一个空对象,将该对象的__proto__指向构造函数的prototype,然后以该对象为上下文执行构造函数,最后根据返回值决定返回对象。

function Animal(type) {
    this.type = type;
}
var a = new Animal('cat');
console.log(a.__proto__ === Animal.prototype); // true
console.log(a instanceof Animal);              // true

// 手动模拟new的过程
function myNew(Constructor, args) {
    var obj = {};
    obj.__proto__ = Constructor.prototype;
    var result = Constructor.apply(obj, args);
    return result instanceof Object ? result : obj;
}

注意instanceof的判断原理正是沿着实例的__proto__一路向上查找,看是否能找到构造函数的prototype,找到了就返回true。理解了这一点,你就明白为什么[] instanceof Object也是true——因为原型链最终会到达Object.prototype。

规则四:属性查找沿原型链进行,顶端是null

访问一个对象的某个属性时,如果对象本身没有这个属性,就会去它的__proto__(也就是构造函数的prototype)里找,还没有就继续往上找__proto__的__proto__,一层层向上,直到找到为止;如果到最顶端还没有,就返回undefined。这条由__proto__串起来的链条就是原型链,链的顶端是Object.prototype.__proto__,其值为null,表示查找到此终止。

console.log(Object.prototype.__proto__ === null); // true

var arr = [1, 2, 3];
// 完整的查找链条:arr -> Array.prototype -> Object.prototype -> null
console.log(arr.hasOwnProperty('length')); // true,来自Object.prototype

// 原型链示例
var base = { city: '北京' };
var child = Object.create(base);
child.name = '小明';
console.log(child.name); // 自身属性:小明
console.log(child.city); // 沿原型链找到:北京
console.log(child.age);  // 全链找不到:undefined

原型链查找有一个经典陷阱:给实例赋值同名属性时,只会遮蔽(覆盖访问)原型上的属性,而不会修改原型本身;但如果直接操作prototype上的引用类型属性(如数组),则会影响到所有实例,这就是所谓原型污染问题。因此写继承时,引用类型的属性一定要放在构造函数里通过this初始化,而不是放在prototype上。

规则五:原型链是实现继承的基础

把前面四条规则组合起来,就得到了js继承的核心思想:让子构造函数的prototype指向父构造函数的一个实例,这样子实例访问属性时就能沿着原型链查找到父类的原型方法。常见的组合继承写法如下:

function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}
Parent.prototype.sayHello = function () {
    console.log('hello, ' + this.name);
};

function Child(name, age) {
    Parent.call(this, name);  // 继承实例属性
    this.age = age;
}
// 继承原型方法,用空函数过渡避免重复调用Parent构造函数
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;

var c1 = new Child('小明', 10);
var c2 = new Child('小红', 12);
c1.sayHello();                  // hello, 小明
c1.colors.push('green');
console.log(c2.colors);         // ['red', 'blue'],互不影响

ES6的class和extends关键字本质上就是上述机制的语法封装,Object.create方法也是直接基于规则三实现的,它创建一个新对象并将其__proto__指向传入的参数。所以说,只要真正吃透这几条原型规则,无论是阅读老代码、排查this和属性查找问题,还是理解各种新语法,都能做到举一反三。

总结一下核心记忆点:引用类型都有__proto__,函数才有prototype;实例的__proto__等于构造函数的prototype;属性查找沿原型链向上直到null;原型链的顶端是Object.prototype。掌握这几条,js原型就不再神秘。

js原型原型链JavaScript面向对象修改时间:2026-09-04 03:28:39

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260904/49980.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。