原型与继承
原型对象
每个对象都有一个原型prototype
对象,通过函数创建的对象也将拥有这个原型对象。原型是一个指向对象的指针。
- 原型包含
constructor
属性,指向构造函数 - 对象包含
__proto__
指向他的原型对象
默认情况下创建的对象都有原型
let hd = { name: "love" };
console.log(hd);
以下 x、y 的原型都为元对象 Object,即 JS 中的根对象
let x = {};
let y = {};
console.log(Object.getPrototypeOf(x) == Object.getPrototypeOf(y)); //true
极简对象(纯数据字典对象)没有原型(原型为 null)
let xj = Object.create(null, {
name: {
value: "love"
}
})
console.log(xj) //{name: 'love'}
console.log(xj.hasOwnProperty("name")) //Error
关系图
下面使用 setPrototypeOf
与 getPrototypeOf
获取与设置原型
let hd = {};
let parent = { name: "parent" };
Object.setPrototypeOf(hd, parent);
console.log(hd);
console.log(Object.getPrototypeOf(hd));
constructor 存在于 prototype 原型中,用于指向构建函数的引用。
function hd() {
this.show = function() {
return "show method";
};
}
const obj = new hd(); //true
console.log(obj instanceof hd);
const obj2 = new obj.constructor();
console.dir(obj2.show()); //show method
使用对象的 constructor
创建对象,即构造函数的的原型可以通过实例对象的反向找到。函数默认prototype
指包含一个属性 constructor
的对象,constructor
指向当前构造函数
function User(name, age) {
this.name = name;
this.age = age;
}
function createByObject(obj, ...args) {
const constructor = Object.getPrototypeOf(obj).constructor;
return new constructor(...args);
}
let hd = new User("zhangsan");
let xj = createByObject(hd, "lisi", 12);
console.log(xj);
综上:
function User(name) {
this.name = name;
}
let xj = new User("zhangsan");
console.log(xj);
console.log(User.prototype.constructor == User); //true 构造函数原型的constructor就是构造函数本身
console.log(xj.__proto__ == User.prototype); //true
let lisi = new xj.constructor("李四");
console.log(lisi.__proto__ == xj.__proto__); //true
原型检测
instanceof 检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
使用isPrototypeOf
检测一个对象是否是另一个对象的原型链中
const a = {};
const b = {};
const c = {};
Object.setPrototypeOf(a, b);
Object.setPrototypeOf(b, c);
console.log(b.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(b)); //true
属性遍历
使用in
检测原型链上是否存在属性,使用 hasOwnProperty
只检测当前对象。
使用 for/in
遍历时同时会遍历原型上的属性如下例
hasOwnProperty
方法判断对象是否存在属性,而不会查找原型。
借用原型
使用 call
或 apply
可以借用其他原型方法完成功能。
this
this
不受原型继承影响,this
指向调用属性时使用的对象。
原型总结
prototype
prototype
是函数对象独有的属性,它指向了一个原型对象。每个函数对象都有一个prototype
属性,但它只有在这个函数作为构造函数使用时才有意义。
function Person() {}
console.log(Person.prototype); // 输出:Person {}
prototype
属性的主要作用是在构造函数模式下,用于构建实例对象的原型链。当我们使用构造函数来创建一个实例对象时,它的__proto__
属性会指向构造函数的prototype
属性。
let person = new Person();
console.log(person.__proto__ === Person.prototype); // 输出:true
上面的代码中,我们使用了Person构造函数来创建了一个对象person。结果表明,person.__proto__
指向了Person.prototype
。
Object.create
使用Object.create
创建一个新对象时使用现有对象做为新对象的原型对象,第二个参数设置新对象的属性
let user = {
show() {
return this.name;
}
};
let hd = Object.create(user, {
name: {
value: "you"
}
});
hd.name = "love";
console.log(hd.show());
proto
__proto__
是对象的一个内置属性,它用于指向该对象的原型。每个对象都有一个__proto__
属性,包括自定义对象、内置对象和函数对象。通过__proto__
属性,我们可以访问和操作对象的原型链。
let obj = {};
console.log(obj.__proto__); // 输出:Object {}
let arr = [];
console.log(arr.__proto__); // 输出:Array []
function func() {}
console.log(func.__proto__); // 输出:[Function]
上面的代码中,我们创建了一个空对象obj,并访问了它的__proto__
属性。可以看到,obj.__proto__
指向了一个Object{}对象。同样,我们还创建了一个空数组arr,并访问了它的__proto_
_属性,结果是arr.__proto__
指向了一个Array []对象。而对于函数对象func来说,它的__proto_
_指向的是一个[Function]对象。
总结起来,__proto__
属性用于指向对象的原型,我们可以通过它来访问和操作原型链。
在实例化对象上存在 proto 记录了原型,所以可以通过对象访问到原型的属性或方法。
__proto__
不是对象属性,理解为prototype
的getter/setter
实现,他是一个非标准定义__proto__
内部使用getter/setter
控制值,所以只允许对象或 null- 建议使用
Object.setPrototypeOf
与Object.getProttoeypOf
替代__proto__
下面修改对象的 __proto__
是不会成功的,因为_proto__
内部使用getter/setter
控制值,所以只允许对象或 null
let xj = {};
xj.__proto__ = "love";
console.log(xj);
下面定义的__proto__
就会成功,因为这是一个极简对象,没有原型对象所以不会影响__proto__
赋值。
let hd = Object.create(null);
hd.__proto__ = "love";
console.log(hd); //{__proto__: "love"}
__proto__
和prototype都与对象的原型相关,它们之间的联系和区别如下:
__proto__
是实例对象的属性,用于指向该对象的原型;而prototype是构造函数的属性,用于指向构造函数的原型对象。__proto_
_是读取并访问对象的原型链的属性,可以在实例对象上直接访问;而prototype是构造函数的属性,只能在构造函数内部访问。__proto__
是非标准的属性,只有部分浏览器支持;而prototype是标准属性,所有的对象和函数都有。__proto__
是获取对象原型的,prototype
是为对象添加原型的属性和方法的。
实例与原型的关系:当一个对象被创建时,它的__proto__
属性会指向其构造函数的prototype
属性。这意味着每个对象的__proto__
属性实际上就是其构造函数的prototype
属性。例如,如果有一个构造函数Person
,创建了一个实例person1
,那么person1.__proto__ === Person.prototype
将会返回true
原型中保存引用类型会造成对象共享属性,所以一般只会在原型中定义方法。
function User() {}
User.prototype = {
lessons: ["JS", "VUE"]
};
const lisi = new User();
const wangwu = new User();
lisi.lessons.push("CSS");
console.log(lisi.lessons); //["JS", "VUE", "CSS"]
console.log(wangwu.lessons); //["JS", "VUE", "CSS"]
为 Object 原型对象添加方法,将影响所有函数
<body>
<button onclick="this.hide()">love</button>
</body>
<script>
Object.prototype.hide = function() {
this.style.display = "none";
};
</script>
对象设置属性,只是修改对象属性并不会修改原型属性,使用hasOwnProperty
判断对象本身是否含有属性并不会检测原型。
function User() {}
const lisi = new User();
const wangwu = new User();
lisi.name = "小明";
console.log(lisi.name);
console.log(lisi.hasOwnProperty("name"));
//修改原型属性后
lisi.__proto__.name = "张三";
console.log(wangwu.name);
//删除对象属性后
delete lisi.name;
console.log(lisi.hasOwnProperty("name"));
console.log(lisi.name);
使用 in
会检测原型与对象,而 hasOwnProperty
只检测对象,所以结合后可判断属性是否在原型中
function User() {
}
User.prototype.name = "后盾人";
const lisi = new User();
//in会在原型中检测
console.log("name" in lisi);
//hasOwnProperty 检测对象属性
console.log(lisi.hasOwnProperty("name"));
使用建议
通过前介绍我们知道可以使用多种方式设置原型,下面是按时间顺序的排列
prototype
构造函数的原型属性Object.create
创建对象时指定原型__proto__
声明自定义的非标准属性设置原型,解决之前通过Object.create
定义原型,而没提供获取方法Object.setPrototypeOf
设置对象原型
这几种方式都可以管理原型,一般来讲使用 prototype
更改构造函数原型,使用 Object.setPrototypeOf
与 Object.getPrototypeOf
获取或设置原型。
构造函数
使用优化
使用构造函数会产生函数复制造成内存占用,及函数不能共享的问题。
function User(name) {
this.name = name;
this.get = function() {
return this.name;
};
}
let lisi = new User("小明");
let wangwu = new User("王五");
console.log(lisi.get == wangwu.get); //false
//优化后
function User(name) {
this.name = name
}
User.prototype.get = function () {
return this.name
}
let lisi = new User("小明")
let wangwu = new User("王五")
console.log(lisi.get == wangwu.get) //true
//通过修改原型方法会影响所有对象调用,因为方法是共用的
lisi.__proto__.get = function () {
return "love" + this.name
}
console.log(lisi.get())
console.log(wangwu.get());
使用Object.assign
一次设置原型方法来复用,后面会使用这个功能实现 Mixin 模式
function User(name, age) {
this.name = name;
this.age = age;
}
Object.assign(User.prototype, {
getName() {
return this.name;
},
getAge() {
return this.age;
}
});
let lisi = new User('李四', 12);
let xiaoming = new User('小明', 32);
console.log(lisi.getName()); //李四
console.log(lisi.__proto__)
我们希望调用父类构造函数完成对象的属性初始化,但像下面这样使用是不会成功的。因为此时 this
指向了 window,无法为当前对象声明属性。
function User(name) {
this.name = name;
console.log(this);// Window
}
User.prototype.getUserName = function() {
return this.name;
};
function Admin(name) {
User(name);
}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.role = function() {};
let xj = new Admin("love");
console.log(xj.getUserName()); //undefined
解决上面的问题是使用 call/apply
为每个生成的对象设置属性
function User(name) {
this.name = name;
console.log(this); // Admin
}
User.prototype.getUserName = function() {
return this.name;
};
function Admin(name) {
User.call(this, name);
}
Admin.prototype = Object.create(User.prototype);
let xj = new Admin("love");
console.log(xj.getUserName()); //love
Mixin 模式
JS
不能实现多继承,如果要使用多个类的方法时可以使用mixin
混合模式来完成。
下面分拆功能使用 Mixin 实现多继承,使用代码结构更清晰。只让 Admin
继承 User
原型
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
sub.prototype.constructor = sub;
}
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.show = function() {
console.log(this.name, this.age);
};
const Credit = {
total() {
console.log("统计积分");
}
};
const Request = {
ajax() {
console.log("请求后台");
}
};
function Admin(...args) {
User.apply(this, args);
}
extend(Admin, User);
Object.assign(Admin.prototype, Request, Credit);
let hd = new Admin("向军", 19);
hd.show();
hd.total(); //统计积分
hd.ajax(); //请求后台
mixin
类也可以继承其他类,比如下面的 Create
类获取积分要请求后台,就需要继承 Request
来完成。
super
是在mixin
类的原型中查找,而不是在User
原型中
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
sub.prototype.constructor = sub;
}
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.show = function() {
console.log(this.name, this.age);
};
const Request = {
ajax() {
return "请求后台";
}
};
const Credit = {
__proto__: Request,
total() {
console.log(super.ajax() + ",统计积分");
}
};
function Admin(...args) {
User.apply(this, args);
}
extend(Admin, User);
Object.assign(Admin.prototype, Request, Credit);
let hd = new Admin("you", 19);
hd.show();
hd.total(); //统计积分
hd.ajax(); //请求后台