原型和原型链
原型和原型链
只有函数才有 prototype
属性,
只有对象才有 __proto__
属性,
构造函数创建对象
function Person () {
this.name = 'name'; // 每次都对实例对象自身做了一个扩展
}
let person = new Person(); // 创建一个空对象,{},并给这个对象的__proto__ 赋值,也就是构造函数的prototype
person.name = 'hello world!';
person.gender = 'man';
console.log(person);
prototype
每个函数都有一个 prototype
属性,如:
function Person () {
this.age = 100;
};
// prototype是函数才会有的属性
Person.prototype.type = 'person';
Person.prototype.age = 0;
let person = new Person();
console.log(person.age); // 100
函数的prototype属性指向了一个对象,这个对象是调用该构造函数而创建实例的原型,也就是该实例person
中的原型;
person.__proto__ === Person.prototype;
当我们创建了一个构造函数的时候,或者声明了一个class
的时候,这个时候这个变量存在一个prototype
对象,
当使用该构造函数去创建实例的时候,这个实例的原型就是这个对象。
构造函数和实例原型之间的关系:
proto
每个JavaScript对象都含有一个属性(隐式原型),叫做__proto__
,该属性指向该对象的原型,
绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype
中,
实际上,它是来自于 Object.prototype
,与其说是一个属性,不如说是一个 getter/setter
,
当使用 obj.__proto__
时,可以理解成返回了 Object.getPrototypeOf(obj)
。
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
constructor
每个原型都有一个 constructor
属性指向关联的构造函数
function Person() {
}
console.log(Person === Person.prototype.constructor); // true
更新关系图如下:
综上:
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
实例与原型
当读取实例的属性时,如果找不到,会从与之关联的原型中的去查找,如果还找不到,会去原型的原型去查找,一直找到 Object
,如果还没有,就返回 undefined
因为到Object
时,此时的 Object.prototype.__proto__
的值为 null
,Object.prototype
没有原型,那么也就不存在任何属性。
console.log(Object.prototype.__proto__ === null) // true
原型的原型
原型链
Object.prototype
的原型
Object.prototype.__proto__ === null
null
代表什么?
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.__proto__
的值为 null
跟 Object.prototype
没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
关系图更新:
由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
原型的缺点
单独使用原型模式的问题
原型中所有属性都是共享的,对于多个实例操作同一个引用类型,指向原型的同一个引用,最终导致原型上的数据发生改变,所有的实例都改变了。
let Person = function () {
}
Person.prototype = {
gender: 'sex',
friends: ['a', 'b', 'c']
}
let p1 = new Person();
let p2 = new Person();
p1.friends.push('d');
console.log(p1.friends); // ['a', 'b', 'c', 'd']
console.log(p2.friends); // ['a', 'b', 'c', 'd']
显然,这不是我们想要的;
可以使用构造函数+原型模式,
构造函数用于定义实例属性,
原型中用于定义共享属性。
let Person = function () {
this.friends = ['a', 'b', 'c'];
}
Person.prototype = {
gender: 'sex',
}
let p1 = new Person();
let p2 = new Person();
p1.friends.push('d');
console.log(p1.friends); // ['a', 'b', 'c', 'd']
console.log(p2.friends); // ['a', 'b', 'c',]
new
new Foo
的过程
1、创建一个新对象,他的原型__proto__
指向构造函数的prototype
2、执行构造函数,重新指定this为该新对象,并传入对应参数
3、返回一个对象,如果构造函数返回一个对象,则返回这个对象,否则返回创建的新对象
编码如下:
// 模拟new的实现
let newMock = function () {
let argus = Array.prototype.slice.apply(arguments);
let Foo = argus.shift();
let target = Object.create(Foo.prototype);
let foo = Foo.apply(target, argus);
if (typeof foo === 'object') {
// 比如构造函数返回了一个Object,工厂模式之类的
return foo;
} else {
return target;
}
}
// 构造函数
let Person = function (name) {
this.name = `i am ${name}`;
this.say = function () {
console.log(this)
}
}
// 使用
let p = newMock(Person, 'www')
console.log(p instanceof Person) // === true 说明newMock使用成功
继承
利用原型让一个引用类型继承另一个引用类型的属性和方法;
借助构造函数
function Person1 (name) {
this.name = name;
}
function Stu1 (name, sno) {
Person1.call(this, name)
this.sno = sno;
}
let stu = new Stu1();
stu instanceof Stu1; // true;
stu instanceof Person1; // false;
stu.constructor // Stu1
缺点:不能够继承父类的原型链
借助原型链
function Person2 (name) {
this.name = name;
this.colors = [1, 2, 3];
}
function Stu2 (sno) {
this.sno = sno;
}
Stu2.prototype = new Person2();
let stu = new Stu2('www', 1);
let stu2 = new Stu2('222', 2);
stu.colors.push(4);
console.log(stu); // colors [1,2,3,4]
console.log(stu2); // colors [1,2,3,4]
console.log(stu instanceof Stu2); // true;
console.log(stu instanceof Person2); // true;
console.log(stu.constructor) // Person2
缺点:因为共享原型属性,有一个原型有修改另一个也跟着修改了
组合式继承
function Person3 (name) {
this.name = name;
}
function Stu3 (name, sno) {
Person3.call(this, name)
this.sno = sno;
}
Stu3.prototype = new Person3();
Stu3.prototype.constructor = Stu3; // 构造函数
let stu = new Stu3();
stu instanceof Stu3; // true;
stu instanceof Person3; // true;
stu.constructor // Stu3
缺点:父类的构造函数执行了两次
原型式继承
ES6新增Object.create方法,规范了原型式继承
function Person4 (name) {
this.name = name;
this.colors = [1, 2, 3];
}
function Stu4 (name, sno) {
Person4.call(this, name)
this.sno = sno;
}
// Stu4.prototype = new Person4(); // 父类的实例 --> 实例化了两次
// Stu4.prototype = Person4.prototype; // 父类的原型 ---> 默认constructor是父类的constructor
Stu4.prototype = Object.create(Person4.prototype); // 原型式继承(隔离父类的原型)
Stu4.prototype.constructor = Stu4; // 构造函数
let stu = new Stu4('www', 1);
let stu2 = new Stu4('222', 2);
stu.colors.push(4);
console.log(stu);
console.log(stu2);
console.log(stu instanceof Stu4); // true;
console.log(stu instanceof Person4); // true;
console.log(stu.constructor) // Stu4