前言

JavaScript是一门基于原型的语言,在软件设计模式中,有一种模式叫做原型模式,JavaScript正是利用这种模式而被创建出来。
原型模式是用于创建重复的对象,同时又能保证性能,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。原型模式的目的是用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,也就是说利用已有的一个原型对象,可以快速地生成和原型对象一样的新对象实例。

概念与理解

原型:一个可以被复制(或者叫克隆)的一个类,通过复制原型可以创建一个一模一样的新对象,也可以说原型就是一个模板,在设计语言中更准确的说是一个对象模板。
原型是定义了一些公用的属性和方法,利用原型创建出来的新对象实例会共享原型的所有属性和方法。

通过Function函数对象创建一个函数(函数对象,也称构造函数对象,下同)以后,解析器都会默认在函数中添加一个属性prototype。prototype属性指向的是一个对象,这个对象我们称为原型对象(这个原型对象是由Object函数对象创建的)。当函数作为构造函数使用,它所创建的对象(实例对象)中都会有一个隐含的属性__proto__指向该原型对象。

而原型对象有个constructor属性,指向的是构造函数对象。

.png

原型对象就相当于一个公共的区域,凡是通过同一个构造函数创建的对象他们通常都可以访问到相同的原型对象。我们可以将对象中共有的属性和方法统一添加到原型对象中,这样我们只需要添加一次,就可以使所有的对象都可以使用。

原型链

当我们去访问对象obj的一个属性或调用对象的一个方法时,它会先在自身中寻找, 如果在自身中找到了,则直接使用。如果没有找到,则去原型对象中寻找,如果找到了则使用。如果没有找到,则将该原型对象作为新的obj,去它的构造函数的原型中寻找(即看看obj.__proto__中有没有),依此类推。当然不会一直找下去,原型链是有终点的。直到找到null为止(Object.prototype.__proto__ === null)。如果依然没有找到则返回undefined。

原型链举例如下:

arr ---> Array.prototype ---> Object.prototype ---> null

这就是传说中的原型链,层层向上查找,最后还没有就返回undefined

hasOwnProperty()这个方法可以用来检查对象自身中是否含有某个属性。它是 是JavaScript 中唯一一个处理属性但是不查找原型链的函数。

语法:对象.hasOwnProperty("属性名")

getOwnPropertyNames()函数:可以获取对象所有的自身属性,返回值是由对象自身属性名称组成的数组,同样不会向上查找对象原型链。

示例代码:

// 创建原型
var Person = function (name) {
    this.name = name;
};

// 原型的方法
Person.prototype.sayHello = function () {
    console.log(this.name + ",hello");
};

// 实例化创建新的原型对象,新的原型对象会共享原型的属性和方法
var person1 = new Person("zhangsan");
var person2 = new Person("lisi");

// zhangsan,hello
person1.sayHello();
// lisi,hello
person2.sayHello();

构造函数 Parent、Parent.prototype 和 实例 p 的关系

p.__proto__ === Parent.prototype

__proto__到底指向什么?

通俗地说,谁创建了包含这个__proto__属性的对象,则__proto__就指向谁的原型对象(prototype)。

prototype 和 __proto__ 区别

通俗地说,__proto__是对象作为实例来讲的一个属性,指向创建该实例的构造函数的原型,而prototype是对象作为构造函数来讲的一个属性,指向其自身的原型。

  1. prototype是Object对象、Function函数对象、所有构造函数对象的属性
  2. __proto__是每个实例对象包括1.中所列的对象都有的属性(实例对象自身只有__proto__属性,没有prototype和constructor属性。但可以通过原型链访问到其__proto__中的constructor属性,该指向构造函数对象(即obj.constructor === obj.__proto__.constructor)。在组合继承中,构造函数的prototype可以指向其他类的实例对象,详见组合继承)
  3. 实例的__proto__与其构造函数对象的prototype指向的是同一个原型对象

所有关系示意图如下:

2023-03-03-18.58.36.png

需要注意

这里需要深入理解:

在创建实例对象A之后再通过其他同类实例对象B或构造函数对象Father改变其原型(如增加一个属性),实例对象A和B都没有在自身作用域中获得新的属性,而是通过原型链找到了其构造函数对象的原型FatherPrototype,在原型中获取了新增加的属性值。且不同实例对象维护不同的属性值。

function func1(){
    this.a = 'a';
    this.b = 'b';
}

var f1 = new func1();
console.log(f1.c); // => undefined

var f2 = new func1();
f2.__proto__.c = 'c';

console.log('\nf1.c: ' + f1.c); // => f1.c: c
console.log('\nf2.c: ' + f2.c); // => f2.c: c

console.log(f1.hasOwnProperty('c')); // => false
console.log(f2.hasOwnProperty('c')); // => false

f2.c = 'd';
console.log('\nf1.c: ' + f1.c); // => f1.c: c
console.log('\nf2.c: ' + f2.c); // => f2.c: d

JavaScript 中的继承

以下只给出目前最优的“寄生组合式”继承方法:

  • 属性继承
function Person (name, age) {
    this.name = name
    this.age = age
}

// 方法定义在构造函数的原型上
Person.prototype.getName = function () { console.log(this.name)}

function Teacher (name, age, subject) {
    Person.call(this, name, age)
    this.subject = subject
}
  • 方法继承
Teacher.prototype = Object.create(Person.prototype)
Teacher.prototype.constructor = Teacher

补充理解

function func1(){
    this.name = "func1";
    let property = "QiuYeDx";
    // console.log("func1!");
}

func1.prototype.subFunc = () => {
    console.log("subFunc!");
}

// console.log(func1, func1.constructor, func1.prototype, func1.__proto__);

let obj = new func1();
// console.log(obj instanceof func1.constructor);   // false

// console.log(Function == func1.constructor); // true
// console.log(func1 instanceof func1.constructor); // true
// console.log(func1 == func1.constructor); // false
// console.log(obj.constructor == func1);  // true

// console.log(func1 == func1.prototype); // false
// console.log(obj.__proto__); // { subFunc: [Function (anonymous)] }
// console.log(obj.__proto__.constructor); // console.log(obj.constructor); 输出[Function: func1]
// console.log(obj.__proto__.constructor.__proto__);   // {}
// console.log(obj.__proto__.constructor.__proto__.constructor);   // [Function: Function]
// console.log(obj.__proto__.constructor.__proto__.constructor.__proto__); // {}
// console.log(obj.__proto__.constructor.__proto__.constructor.__proto__.constructor); // [Function: Function]

// console.log(obj.__proto__); // { subFunc: [Function (anonymous)] }
// console.log(obj.__proto__.__proto__); // [Object: null prototype] {}
// console.log(obj.__proto__.__proto__.__proto__); // null
// console.log(obj.__proto__.__proto__.constructor);   // [Function: Object]
  • obj.constructorobj.__proto__.constructor相同,都是func1

  • 原型链是一路向上调用__proto__属性的一个调用链,也是访问一个对象的属性或方法所检查是否能正常调用到的一个检查链。

相关资料

[1]: 对于js的原型和原型链的理解

[2]: 原型和原型链的理解(有图清晰明了)

[3]: 说说原型(prototype)、原型链和原型继承

[4]: 关于原型链继承中的constructor


A Student on the way to full stack of Web3.