原型+继承【JS深入知识汇点6】

309 阅读4分钟

原型

原型本质

当某个对象,承担了为其他对象提供共享属性的职责时,它就成了该对象的 prototype。换句话说,如果不跟其他对象产生关联,就不构成 prototype 这个称谓。

所以,prototype 描述的是两个对象之间的某种关系(其中一个为另外一个提供属性访问权限)

当读取实例属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还没找到,就会继续在原型的原型上找,直到找到最顶层。

proto 、 prototype、 constructor

  • __proto__ 和 constructor 是对象独有的。
  • prototype 属性是函数独有的(函数也是对象)
  • 函数创建的对象.__proto__ === 函数.prototype
  • 函数创建的对象.constructor === 函数(往原型链上找)
  • 函数.prototype.constructor === 函数

prototype

当创建函数时,js 会为这个函数自动添加 prototype 你属性,值是一个有 constructor 属性的对象,不是空对象。当把函数当作构造函数调用,那么 js 就会在创建实例的同时,实例继承构造函数的 prototype 的所有属性和方法。

  • Object.getPrototypeOf(obj): 访问指定对象的 prototype
  • Object.setPrototypeOf(obj, otherObj): 设置指定对象的 prototype 对象

作用是:包含可以给特定类型的所有实例提供共享的属性和方法

__proto__

ES 规范里,prototype 是一个隐式引用,但之前,在一些浏览器中,私自实现了 __proto__ 属性,使得可以通过 obj.__proto__ 这个显式的属性访问,访问到被定义为隐式属性的 prototype。 每个 js 对象一定对应一个原型对象,并从原型对象继承属性和方法继承方法和属性。

  • obj.__proto__: 访问指定对象的 prototype
  • obj.__proto__ = otherObj: 设置指定对象的 prototype 对象
  • __proto__ 属性既不能被for in ,也不能被Object.keys(obj) 查找出来

constructor

constructor 返回创建实例对象时构造函数的引用,是一个对象指向一个函数。 每一个原型都有一个 constructor 属性指向关联的构造函数。

function Person() {}
console.log(Person === Person.prototype.constructor)

typeof

用于判断一个变量的类型。返回一个字符串。

类型 结果
Undefined "undefined"
Boolean "boolean"
Number "number"
String "string"
Functiion "function"
Null "object"
Array "object"
其他任何对象 "object"

null 的 typeof 值是 "object",这是因为 js 底层存储变量类型时,对象的类型标签是 0,由于 null 代表的是空指针(大部分平台值为 0x00),所以类型标签是 0, typeof null 也因此返回 "object"

Object.prototype.toString.call()

返回一个表示该字段的字符串

类型 结果
Undefined "[object Undefined]"
Boolean "[object Boolean]"
Number "[object Number]"
String "[object String]"
Functiion "[object Function]"
Null "[object Null]"
Array "[object Array]"
其他任何对象 "[object Object]"

instanceof

instanceof 用于检测 func.prototype 属性是否出现在实例对象的原型链上,也就是判断一个实例是否是其父类型或者祖先类型的实例。 实现原理就是 右边变量的 prototype 在左边变量的原型链上即可,所以在查找的过程中,会遍历左边变量的原型链,直到找到右边变量的 prototype,如果没找到,会返回 false。

有几个例子:

function Foo() {}
Object instanceof Object  //true  Object 本身是一个函数
Function instanceof Function  //true
Function instanceof Object  //true
Foo instanceof Object //true
Foo instanceof Foo  //false  Foo.__proto__ = Function.prototype  
Foo instanceof Function //true 

原型链

每个对象都有一个原型对象,通过__proto__ 指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null,这种关系称为原型链

先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数继承Function.prototype而产生。

继承

在 js 中,继承的本质就是复制,即重写原型对象,代之以一个新类型的实例。

  • 原型链继承:设置 xxx.prototype = xx
  • 构造函数继承:使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类 var xxx = new xx()
  • 组合继承:用原型链实现对原型属性和方法的继承,借用构造函数技术来实现实例属性的继承
  • 原型式继承:继承的 object 方法本质上是对参数对象的一个浅复制.var xxx = Object.create(xx)
  • 寄生式继承:使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。
  • 寄生组合继承
  • ES6 Class Extends: 是一种语法糖,先将父类实例对象的属性和方法,加到 this 上面,然后再用子类的构造函数修改 this。
class A {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}
const xxx = new A(10, 100)

class B extends A {
    constructor(length) {
        // 如果子类中存在构造函数,则需在使用 this 之前首先调用 super
        super(length, length)
    }
}

好题分享

Q1:写出下面的答案

function Person() {}
let personl = new Person();

person1.__proto__ = ? // Person.prototype
Person.__proto__ = ? // Function.prototype
Person.prototye.__proto__ = ? // Objet.prototype
Object.__proto__ = ? // Object.prototype
Object.prototype.__proto__ = ? //null

Q2:写出下面的答案

function SuperType(name) {
    this.name = name;
}
SuperType.prototype.sayName = function() {
    alert(this.name)
}
function SubType(name, age) {
    // 通过调用父构造函数的 call 方法来实现继承
    SuperType.call(this, name)
    this.age = age;
}
SubType.prototype.sayAge = function() {
    alert(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black');
instance1.sayName(); // "Nicholas"
instance1.sayAge(); // 29