关于JS原型、原型链和继承的问题
声明一个对象可以var obj = {},这是字面意思;或者var obj = new Object(),Object为一个构造函数,是指创建一个空对象,这个空对象把Object.prototype(Object的原型对象)克隆了过来,用自己的__proto__储存了起来,obj称为Object的实例。两者的本质是一样的,同样都声明了一个新对象。所有新对象都是克隆了其构造函数的原型对象到自身的__proto__属性身上。通过对象的__proto__无法修改构造函数的原型prototype,但是通过prototype可以修改其实例化对象的原型__proto__。
一、原型
当我们需要创建多个对象时,每创建一个对象就要给这个新的对象添加属性或方法,尽管有的属性或方法是相同的。这个时候我们可以把共有的属性方法写到构造函数的原型对象(prototype)上,每创建多个具有相同属性的对象时,克隆构造函数的原型对象就好。每个构造函数都有自己的原型对象,每个对象也有自己的原型(__proto__属性),__proto__也是一个对象,克隆自构造函数的原型对象。
例1:
1 function Person(name){
2 this.name = name;
3 }
4 Person.prototype.say = function() {
5 //定义构造函数的原型。注意:要写在实例化之前
6 console.log("I am" + this.name);
7 }
8 var p1 = new Person("xiaoming");
9 var p2 = new Person("xiaowang");
10 console.log(p1.name) //xiaoming;
11 console.log(p2.name) //xiaowang;
12 p1.say(); //I am xiaomin
13 p2.say() ;//I am xiaowang
此时p1 = {},p2 = {},没有任何属性和方法,却可以访问say()方法,这是因为p1,p2身上的隐式属性__proto__克隆了构造函数的原型Person.prototype,前面我们给Person.prototype对象添加了say()方法。当我们p1.say()调用时,自身没有会去自己的__proto__上查找,所以p1,p2能够访问say()方法。
new关键字做了什么?
1.创建一个全新的空对象,并从其构造函数原型身上复制属性和方法到自己的原型__proto__身上;
2.执行构造函数,把构造函数的内部的this指向创建完的新对象;
3.构造函数默认返回这个对象
重写new关键字:
function Person(name){
this.name = name;
}
Person.prototype.say = function() {
console.log("I am" + this.name);
}
function _new(){
var Foo = [].shift.call(arguments)
var obj = Object.create(Foo.prototype)
Foo.apply(obj,arguments)
return obj
}
var p3 = _new(Person,'xiaoming')
二、原型链
每个构造函数都有自己的原型prototype,而每个对象则有自己的原型__proto__=== 其构造函数的prototype,prototype也是一个对象,是对象就有自己的构造函数,也有自己的原型__proto__(等同于构造函数的prototype)。当我们使用对象上的属性时,首先会从自己身上查找,自身没有会去自己的原型__proto__身上找,__proto__上没有会去__proto__.__proto__上找.....最终会找到Object.prototype,这个关系就叫原型链。通过测试我们可发现:
1 p1.__proto__ === Person.prototype //true
2 Person.prototype.__proto__ === Object.prototype //true
3 Object.prototype.__proto__ === null //true
三、继承
当不同的对象身上有着不同的方法和属性时,如果我们希望在此对象使用彼对象的属性和方法又该怎么做?
1、通过new实现原型链继承
function SuperType() {
this.name = 'super';
this.colors = ["red", "blue", "green"];
}
function SubType(name){
this.name = name;
} //继承了 SuperType SubType.prototype = new SuperType();
var sub1 = new SubType('sub1'); var sub2 = new SubType('sub2'); sub1.colors.push("black"); console.log(sub1.name); //sub1 console.log(sub2.name); //sub2 console.log(sub1.colors); //"red,blue,green,black" console.log(sub2.colors); //"red,blue,green,black"
- 优点
实现方式简洁,父类新增原型属性和方法,子类都能访问到。 - 缺点
(1) 原型属性在所有实例中是共享的,修改任意实例属性会影响其他实例属性的值。
(2) 在创建子类型的实例sub1
时,不能向超类型的构造函数SuperType
中传递参数。实际上, 应该说是没有办法在不影响所有对象实例的情况下,给SuperType
传递参数。
(3)子类实例原型上同样也克隆了SuperType的私有属性this.name,这是不必要的,因为SubType也有自己的this.name。
2、Object.create实现继承
function Father(){
this.name = 'father';
}
Father.prototype.say = function(){
console.log('say hello')
}
function Son(){}
Son.prototype = Object.create(Father.prototype, {name: {value: 'son'}});
var son = new Son();
son.say();//"say hello'
- 优点
实现方式简洁,父类新增原型属性和方法,子类都能访问到。不会克隆父类的name属性到子类实例的原型链上。 - 缺点
(1) 原型属性在所有实例中是共享的,修改任意实例属性会影响其他实例属性的值。
(2) 在创建子类型的实例时,不能向父类传递参数。
3、组合继承
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() {console.log(this.name);}; function SubType(name, age) { //借助构造函数继承属性 SuperType.call(this, name); //调用一次 SuperType 构造函数 this.age = age; } //原型链方式继承 sayName 方法 SubType.prototype = new SuperType(); //再次调用 SuperType 构造函数 SubType.prototype.constructor = SubType; //将 constructor指回 SubType SubType.prototype.sayAge = function() {console.log(this.age);}; var sub1 = new SubType("Nicholas", 29); var sub2 = new SubType("Greg", 27); sub1.colors.push("black"); console.log(sub1.colors); //"red,blue,green,black" sub1.sayName(); //"Nicholas"; sub1.sayAge(); //29 console.log(sub2.colors); //"red,blue,green" sub2.sayName(); //"Greg"; sub2.sayAge(); //27
- 优点
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性 - 缺点
SubType的构造函数会被调用两次
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现