一、 问题
- 下面代码执行结果?(原型prototype)
function Test() {}
const t1 = new Test();
console.log(t1.__proto__ === Test.prototype);
console.log(Test.prototype.constructor === Test);
console.log({}.constructor === Object);
console.log(Test.prototype.__proto__ === Object.prototype);
根据原理中的图示,可知打印的结果都是true。
- 下面代码执行结果?(对象的私有属性和原型属性)
function Test(name) {
this.name = name;
}
Test.prototype = {
func: 'Test'
};
const t1 = new Test('t1');
const t2 = new Test('t2');
console.log(t1.name);
console.log(t2.name);
console.log(t1.func);
console.log(t2.func);
console.log(t1.test);
t1.name = 't11';
console.log(t1.name);
console.log(t2.name);
t1.func = 'Test1';
console.log(t1.func);
console.log(t2.func);
答案:
console.log(t1.name); // t1 私有属性
console.log(t2.name); // t2 私有属性
console.log(t1.func); // Test 原型属性
console.log(t2.func); // Test 原型属性
console.log(t1.test); // undefined 私有属性中没有,原型中也没有
t1.name = 't11';
console.log(t1.name); // t11 修改了私有属性
console.log(t2.name); // t2 一个实例的属性修改不影响其他实例的私有属性
t1.func = 'Test1';
console.log(t1.func); // Test1 t1增加了私有属性,覆盖了原型属性
console.log(t2.func); // Test t2访问的还是原型属性
- 下面代码执行结果?(原型链继承)
function Parent() {
this.name = 'parent';
}
function Child() {
this.age = 11;
}
Child.prototype = new Parent();
const c = new Child();
console.log(c.age, c.name);
由原型继承的原理,可知打印结果是“11, parent”。age是私有属性,name是原型属性。
二、 概念和原理
面向对象
构造函数和对象
js可以通过构造函数创建对象:
function Test() {}
const t = new Test();
Test被称为“类”或者“构造函数”。Test是t的构造函数。
也可以定义字面量对象
const a = {};
// 等价于
const a = new Object();
a的构造函数是Object。
我们自己定义构造函数时候,可以给对象添加属性
function Test(name) {
this.name = name;
}
const t1 = new Test('t1');
console.log(t1.name); // t1
new的原理
使用new操作符调用构造函数,做了下面这些工作:
- 创建了一个空的js对象(即{})
- 将空对象的原型prototype指向构造函数的原型
- 将空对象作为构造函数的上下文(改变this指向)
- 判断构造函数的返回值,以决定最终返回的结果。
- 如果返回值是基础数据类型,则忽略返回值;
- 如果返回值是引用数据类型,则使用return 的返回,也就是new操作符无效;
所以,任何一个构造函数的原型都是Object的实例。
function Test() {}
console.log(Test.prototype instanceof Object); // true
原型链
使用构造函数生成的对象,和构造函数之间有一些关系:
(1) 构造函数有个prototype对象(原型),该对象有个“constructor”属性,指向构造函数
(2) 每个对象都有一个“**proto** ”属性,指向它的构造函数的“prototype”属性
(3) 构造函数的prototype对象,也有一个“**proto** ”对象,它指向Object的prototype对象
见图示
当我们访问对象中的属性时候,会先访问该对象中的本身的属性(私有属性),如果访问不到,会查找对象的“**proto** ”指向的构造函数的prototype对象,如果其中有要访问的属性,就使用该值,否则继续访问prototype的“**proto** ”,在其中查找要访问属性。这样一直上溯到Object对象。这个就是“原型链”。
原型链提供了继承的能力。
继承
继承用来实现代码复用,即子类继承父类的属性和方法,就实现了父类代码的复用。
使用原型链如何实现继承呢?如果我们有一个父类Parent,Parent有一个方法,打印自己的name属性。
function Parent() {
this.showName = function () {
console.log(this.name);
}
}
如果我们希望子类可以继承父类的方法,如何实现?通过前面介绍的原理,子类对象查找属性时候如果在自己私有属性中访问不到,会到它构造函数的prototype属性中查找,那么我们给子类构造函数的prototype赋值为父类对象,就可以让子类也可以访问父类的方法“showName”了。
function Child() {
this.name = 'child';
}
Child.prototype = new Parent();
const c = new Child();
c.showName();
当然使用原型链继承无法给父类传递参数,所以在实际应用中,需要结合其他继承方法。
网友评论