Skip to content

原型和原型链

原型和原型链

原型和原型链

只有函数才有 prototype属性,

只有对象才有 __proto__属性,

构造函数创建对象

javascript

function Person () {
    this.name = 'name'; // 每次都对实例对象自身做了一个扩展
}
let person = new Person(); // 创建一个空对象,{},并给这个对象的__proto__ 赋值,也就是构造函数的prototype
person.name = 'hello world!';
person.gender = 'man';
console.log(person);

prototype

每个函数都有一个 prototype属性,如:

javascript

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中的原型;

javascript
person.__proto__ === Person.prototype;

当我们创建了一个构造函数的时候,或者声明了一个class的时候,这个时候这个变量存在一个prototype对象,
当使用该构造函数去创建实例的时候,这个实例的原型就是这个对象。

构造函数和实例原型之间的关系:

image.png

proto

每个JavaScript对象都含有一个属性(隐式原型),叫做__proto__,该属性指向该对象的原型,

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,
实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter
当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)

javascript
function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

image.png

constructor

每个原型都有一个 constructor 属性指向关联的构造函数

javascript
function Person() {

}
console.log(Person === Person.prototype.constructor); // true

更新关系图如下:
image.png

综上:

javascript
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__ 的值为 nullObject.prototype 没有原型,那么也就不存在任何属性。

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

原型的原型

image.png

原型链

Object.prototype 的原型

javascript
Object.prototype.__proto__ === null

null代表什么?

null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.__proto__ 的值为 nullObject.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

关系图更新:
image.png

由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

原型的缺点

单独使用原型模式的问题

原型中所有属性都是共享的,对于多个实例操作同一个引用类型,指向原型的同一个引用,最终导致原型上的数据发生改变,所有的实例都改变了。

javascript
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']

显然,这不是我们想要的;

可以使用构造函数+原型模式,
构造函数用于定义实例属性,
原型中用于定义共享属性。

javascript
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、返回一个对象,如果构造函数返回一个对象,则返回这个对象,否则返回创建的新对象

编码如下:

javascript
// 模拟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使用成功

继承

利用原型让一个引用类型继承另一个引用类型的属性和方法;

借助构造函数

javascript
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

缺点

不能够继承父类的原型链

借助原型链

javascript
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

缺点

因为共享原型属性,有一个原型有修改另一个也跟着修改了

组合式继承

javascript
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方法,规范了原型式继承

javascript
 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

原型与 in 操作符

对象属性判断

  • in

用于判断属性是否在对象中,包括原型链上的属性

  • hasOwnProperty

判断属性是否在对象实例上

  • hasPrototypeProperty

判断属性是否在原型上

遍历

  • for in

遍历对象所有可枚举属性,包括实例属性原型属性

  • Object.keys

获取对象所有可枚举实例属性,不包括原型属性

  • Object.getOwnPropertyNames

获取对象所有的实例属性,包括不可枚举的

js
const obj = {
  a: 1,
  b: 2
};

Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false
});

const symbolKey = Symbol('key');
obj[symbolKey] = 4;

console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c', Symbol(key)]
console.log(Object.keys(obj)); // ['a', 'b']