Javascript

Javascript(三)之原型继承理解

2020-06-13  本文已影响0人  正在刷新log

进阶路线

image.png

3 原型继承

3.1 优秀文章

  1. 最详尽的 JS 原型与原型链终极详解 一
  2. 最详尽的 JS 原型与原型链终极详解 二
  3. 最详尽的 JS 原型与原型链终极详解 三
  4. 【THE LAST TIME】一文吃透所有JS原型相关知识点
  5. 代码复用模式

3.2知识点总结

以下讨论都以Person构造函数为例

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');

3.2.1 对象和函数

1、万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象
2、构造函数的实例都有(constructor)属性,该属性指向构造函数

person1.constructor == Person

3.2.2 原型对象--protoptye

在规范里,prototype 被定义为:给其它对象提供共享属性的对象。
prototype 自己也是对象,只是被用以承担某个职能罢了.

1、每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。【只有函数对象才有 prototype 属性】
2、原型对象(Person.prototype)是构造函数的一个特殊实例,所以有一个constructor属性指向构造函数

Person.prototype.constructor == Person
person1.constructor == Person --对比细品

3、原型对象其实就是普通对象,但 Function.prototype 除外,它是函数对象

function Person(){};
 console.log(Person.prototype) //Person{}
 console.log(typeof Person.prototype) //Object
 console.log(typeof Function.prototype) // Function,这个特殊
 console.log(typeof Object.prototype) // Object
 console.log(typeof Function.prototype.prototype) //undefined

3.2.3 [__ proto __] 指向构造函数的原型对象

1、JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做proto 的内置属性,用于指向创建它的构造函数的原型对象

person1.__proto__ == Person.prototype
image.png

根据上面的图片可以得到以下结论

Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

3.2.4 构造器

1、构造函数(Object)本身就是一个函数(就是上面说的函数对象),它和上面的构造函数 Person 差不多,可以创建对象的构造器不仅仅有 Object,也可以是 Array,Date,Function等

ar obj = new Object()
obj.constructor === Object
obj.__proto__ === Object.prototype

var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;

var c = new Date(); 
c.constructor === Date;
c.__proto__ === Date.prototype;

var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;

这些构造器都是函数对象


image.png

3.2.5 原型链

1、person1.__proto__ 是什么?
    答:person1.__proto__ == Person.prototype

2、Person.__proto__ 是什么?
    答:Person.__proto__ == Function.prototype
       因为 Person.__proto__ === Person的构造函数.prototype
       因为 Person的构造函数 === Function
       所以 Person.__proto__ === Function.prototype

3、Person.prototype.__proto__ 是什么?
     答:Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。
        因为一个普通对象的构造函数 === Object
        所以 Person.prototype.__proto__ === Object.prototype

4、Object.__proto__ 是什么?
    答:Object.__proto__ == Function.prototype(原因同第二题)

5、Object.prototype__proto__ 是什么
    答:Object.prototype 对象也有proto属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。
        Object.prototype.__proto__ === null

3.2.6 函数对象

1、所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function)

Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true

String.__proto__ === Function.prototype  // true
String.constructor == Function //true

// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true

RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

2、所有的构造器都来自于 Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了·Function.prototype·的属性及方法。Function.prototype也是唯一一个typeof XXX.prototype为 function的prototype。其它的构造器的prototype都是一个对象

console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype)   // object
console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object
console.log(typeof Object.prototype)   // object

3、知道了所有构造器(含内置及自定义)的proto都是Function.prototype,那Function.prototype的proto是谁呢 Object.prototype的proto是谁?

Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null // true

4、原型链的形成是真正是靠proto 而非prototype

1)Object.setPropertyOf,给我两个对象,我把其中一个设置为另一个的原型。

2)Object.create,给我一个对象,它将作为我创建的新对象的原型。

3.2.7 问答

面试官:谈谈你对 JS 原型和原型链的理解?

候选人:JS 原型是指为其它对象提供共享属性访问的对象。在创建对象时,每个对象都包含一个隐式引用指向它的原型对象或者 null。

原型也是对象,因此它也有自己的原型。这样构成一个原型链。

面试官:原型链有什么作用?

候选人:在访问一个对象的属性时,实际上是在查询原型链。这个对象是原型链的第一个元素,先检查它是否包含属性名,如果包含则返回属性值,否则检查原型链上的第二个元素,以此类推。

面试官:那如何实现原型继承呢?

候选人:有两种方式。一种是通过 Object.create 或者 Object.setPrototypeOf 显式继承另一个对象,将它设置为原型。

另一种是通过 constructor 构造函数,在使用 new 关键字实例化时,会自动继承 constructor 的 prototype 对象,作为实例的原型。

在 ES2015 中提供了 class 的风格,背后跟 constructor 工作方式一样,写起来更内聚一些。

3.2.8 继承

继承的目的是代码复用,继承只是代码复用的一种手段或者实现方式

一个在构造函数上常用的规则是,用于复用的成员(译注:属性和方法)应该被添加到原型上。

3.2.8.1 类式继承1——默认模式

子类构造函数的原型对象指向父类构造函数的实例实现继承

// 定义继承函数
function inherit(C, P) {
    C.prototype = new P();
}
//parent构造函数
function Parent(name) {
    this.name = name || 'Adam';
}

//给原型增加方法
Parent.prototype.say = function () {
    return this.name;
};

//空的child构造函数
function Child(name) {}

//继承
inherit(Child, Parent);

var child1 = new Child("hh");
alert(child1 .hasOwnProperty('name')); // false

缺点

3.2.8.2 构造函数继承

下面这种模式解决了从子对象传递参数到父对象的问题。它借用了父对象的构造函数,将子对象绑定到this,同时传入参数.使用这种模式时,只能继承在父对象的构造函数中添加到this的属性,不能继承原型上的成员。使用借用构造函数的模式,子对象通过复制的方式继承父对象的成员,而不是像类式继承1中那样获得引用。下面的例子展示了这两者的不同:

function Child(a, c, b, d) {
    Parent.apply(this, arguments);
}

//父构造函数
function Article() {
    this.tags = ['js', 'css'];
}
//StaticPage通过借用构造函数的方式从Article继承
function StaticPage() {
    Article.call(this);
}
var page = new StaticPage();
alert(page.hasOwnProperty('tags')); // true

利用借用构造函数模式实现多继承

function Cat() {
    this.legs = 4;
    this.say = function () {
        return "meaowww";
    }
}

function Bird() {
    this.wings = 2;
    this.fly = true;
}

function CatWings() {
    Cat.apply(this);
    Bird.apply(this);
}

var jane = new CatWings();
console.dir(jane);

优缺点

3.2.8.3 组合式继承

综合以上两种模式,首先借用父对象的构造函数,然后将子对象的原型设置为父对象的一个新实例。这样做的好处是子对象获得了父对象自己的成员,也获得了父对象中可复用的(在原型中实现的)方法。子对象也可以传递任何参数给父构造函数。这种行为可能是最接近Java的,子对象继承了父对象的所有东西,同时可以安全地修改自己的属性而不用担心修改到父对象

function Child(a, c, b, d) {
    Parent.apply(this, arguments);
}
Child.prototype = new Parent();

缺点

测试

//父构造函数
function Parent(name) {
    this.name = name || 'Adam';
}

//在原型上添加方法
Parent.prototype.say = function () {
    return this.name;
};

//子构造函数
function Child(name) {
    Parent.apply(this, arguments);
}
Child.prototype = new Parent();

var kid = new Child("Patrick");
kid.name; // "Patrick"
kid.say(); // "Patrick"
delete kid.name;
kid.say(); // "Adam"
.3.2.8.4 原型式继承(共享原型)

F()函数是一个空函数,它充当了子对象和父对象的代理。F()的prototype属性指向父对象的原型。子对象的原型是一这个空函数的一个实例

function inherit(C, P) {
    var F = function () {};
    F.prototype = P.prototype;
    C.prototype = new F();
}

这种模式通常情况下都是一种很棒的选择,因为原型本来就是存放复用成员的地方。在这种模式中,父构造函数添加到this中的任何成员都不会被继承。
我们来创建一个子对象并且检查一下它的行为:

var kid = new Child();

如果你访问kid.name将得到undefined。在这个例子中,name是父对象自己的属性,而在继承的过程中我们并没有调用new Parent(),所以这个属性并没有被创建。当访问kid.say()时,它在3号对象中不可用,所以在原型链中查找,4号对象也没有,但是1号对象有,它在内在中的位置会被所有从Parent()创建的构造函数和子对象所共享。

存储父类(Superclass)
在上一种模式的基础上,还可以添加一个指向原始父对象的引用。这很像其它语言中访问超类(superclass)的情况,有时候很方便。

function inherit(C, P) {
    var F = function () {};
    F.prototype = P.prototype;
    C.prototype = new F();
    C.uber = P.prototype;
}

重置构造函数引用
这个近乎完美的模式上还需要做的最后一件事情就是重置构造函数(constructor)的指向,如果不重置构造函数的指向,那所有的子对象都会认为Parent()是它们的构造函数,而这个结果完全没有用

// parent, child, inheritance
function Parent() {}
function Child() {}
inherit(Child, Parent);

// testing the waters
var kid = new Child();
kid.constructor.name; // "Parent"
kid.constructor === Parent; // true

原型继承的最终实现

function inherit(C, P) {
    var F = function () {};
    F.prototype = P.prototype;
    C.prototype = new F();
    C.uber = P.prototype;
    C.prototype.constructor = C;
}
3.2.8.5 现代继承之原型继承

让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类进来,在这里,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}  
//需要继承的对象
var parent = {
    name: "Papa"
};

//新对象
var child = object(parent);

//测试
alert(child.name); // "Papa"

在ECMAScript 5中,原型继承已经正式成为语言的一部分。这种模式使用Object.create方法来实现。换句话说,你不再需要自己去写类似object()的函数,它是语言原生的了:

var child = Object.create(parent);
上一篇下一篇

猜你喜欢

热点阅读