[JavaScript]原型、原型链、构造函数与继承

updateTime: 2019-4-11 01:15javascript

updateContent: 继承的实现,优化部份内容细节html

updateTime: 2019-4-15 00:11java

updateContent: 解析完美继承,es6继承内部一探es6

updateTime: 2019-4-17编程

updateContent: js内置类型构建过程json

一 前言

读了本文后,你会对js中的属性追溯机制,继承原理,构造函数和类,万物皆对象,有必定深度理解。c#

为何要读这些? 为了提升代码的复用,js中的继承,函数封装,复制extends,混入mixin,借用call/apply,都是为了代码的复用,为了更高效和更省时的编程!一个好的FEer必须跨过这些!segmentfault

二 原型 prototype

what

js在es6以前中没有类的关键词,在一些须要进行面向对象编码场景中经过js中一种引用数据类型Function(函数类型)来实现类。原型,英文名prototype,一般出如今一个函数类型的属性中,看遍六大基本类型,三大引用类型,惟有函数类型天生拥有属性prototype。这个属性被称为原型。api

why

js为了实现继承而设计了原型链,经过对原型链的查找,来实现公共区域属性方法公用,实例间方法属性私用的目的。原型对象,就是一些公用方法和属性的集合处。浏览器

能够经过简单的几个公式

  • 函数.prototype === 原型
  • 构造方法函数.prototype===原型  
  • 原型.constructor===该函数对象的构造方法   
  • 实例的__proto__===构造方法.prototype
  • Foo.prototype === Foo.prototype .constructor.prototype === fooInstance.__proto__
  • fooInsatance.constructor === Foo.prototype.constructor

是否是很好奇这几个公式怎么来的,公式的源式通常源自现实实践(工)的总结或是理论规范(理)的制定,而这几个公式的源式就来自于咱们的章节六——js内置类型构建过程。

-为何我要用构造方法函数这么拗口的名称来称呼constructor?(便于区分概念引入class)

how

prototype做为一个函数对象所拥有的对象类型属性,他内部有什么呢,ok,上码

function Foo () {
}
console.dir(Foo)
/*
  ƒ Foo()
    arguments: null
    caller: null
    length: 0
    name: "Foo"
    prototype: {constructor: ƒ}constructor: ƒ ()
    __proto__: Object__proto__: ƒ ()apply: ƒ apply()arguments: (...)bind: ƒ bind()call: ƒ call()caller: (...)constructor: ƒ Function()length: 0name: ""toString: ƒ toString()Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]()get arguments: ƒ ()set arguments: ƒ ()get caller: ƒ ()set caller: ƒ ()__proto__: Object[[FunctionLocation]]: <unknown>[[Scopes]]: Scopes[0]
    [[FunctionLocation]]: VM535:1
    [[Scopes]]: Scopes[1]
*/
复制代码

能够看到,输出了prototype中拥有constructor属性,而这个对象指向的就是Foo自己!

固然还有一个属性,那就是做为对象都会拥有的__proto__!

三 原型链

一个对象的__proto__指向该对象的构造函数的prototype,该构造函数的prototype中有一个__proto__继续指向该构造函数的原型对象(父类)的prototype,一直到最顶级的对象(基类)Object的——原型。

为何Object是万物

即便是天不生我Object也得靠着__proto__来寻找父类原型(Function.prototype)。可是,做为创造者的Function类,却也得按照js的规范定义中来走,即全部对象经过__proto__追溯,在尽头大门前坐着的守护者,永远是——Object。

按照尽头守护原理(此句可忽略),规范产生了如下定义,Function.__proto__ === Object.prototype, 而, 门外的伪神,则只有守护者Object才能够经过自身被赋予的prototype来直接触摸代理神的原型,而代理神的原型就是——null(神,又名空空,js的本源,js世界的尽头)!

这里解释下小标题: 由于Object是万物的原型链的查找源,即万物均可以从Object上去找他们对应的一些公共特性,那么万物皆对象~(顺便猜下为何伪神和神没有被传开,由于是做为世界的抽象级别存在,比较难理解,因此没有宣扬)

世界从某个角度来看分为静和动,全部的物种类(静)仍是动做类(动),所建立的引用数据类型,在追溯(findFather)的动做上,都是按照如下两个步骤来递归执行:

1. 判断本次所指向的prototype是否为是尽头(神,null),若为尽头则执行返回 

2. 若不为尽头,则执行.__proto继续向上查找

findFaher (obj) { // 用一行代码来描述就是:

  obj.__proto__ === null ? (() => {})() : findFather(obj.__proto)

}

OneDemo

let objOne = {}
function FnOne () {}
let fnOneInstance = new FnOne()
function findChain (obj, key) {  
    console.info(Object.prototype.toString.call(obj))
    obj.__proto__ === null ? (()=>{})() : findChain(obj.__proto)
}
findChain(objOne)
findChain(fnOneInstance)
findChain(FnOne)
findChain(Object)
console.log(typeof objOne)
console.log(typeof fnOneInstance)
console.log(typeof FnOne)
console.log(typeof Object)
// 好奇你的脑机输出结果与实际结果是否对等吗?好奇的话就快点复制代码打开调试器,黏贴回车看一看吧!=-=复制代码

上面的例子中,FnOne类和Object类的父类原型是Function,而为何FnOne类的对象fnOneInstance输出的原型链上为何没有Function呢?一块儿来看看做为构造函数在new的过程作了什么吧

函数对象

屌屌的Function函数对象我指我本身,即Function.__proto===Function.prototype,为何呢,由于Function即有属性prototype又有__proto__同时也是属于js变量中的一员,由于prototype的强特征,因此叫函数对象。

属性查找机制(先从自己找,没有后在原型链上找,没有则为未定义)就是利用了原型链,一样继承共有成员也是利用了原型和原型链。

私有成员即该对象的成员(由new中第三步,Father.call(objInstance)来添加(this.xxx = xxx;))

有趣的地方,啊,无限循环的条件必然是有一个等式存在(无限递归),否则就是悖论!

Function.__proto__.constructor === Function

Object.__proto__.__proto__.constructor === Object

a = {}

1. a.__proto__ === Object.prototype

b = new Foo()

1. b.__proto__ === Foo.prototype

2. b.__proto__.__proto__ === Foo.prototype.__proto__ === Object.prototype (did u find it ? b not find the prototyperty of Function in find prototypeChain.)

but 

Foo.__proto__ === Function.prototype

Object.__proto__ === Function.prototype === Function.__proto__

Function.__proto__ === Function.prototype

Foo.__proto__ === Function.__proto__ === Object.__proto__

Function.prototype .__proto === Object.prototype === Object.__proto__.__proto__ === Foo.__proto__._proto__

Object.prototype.__proto__ === null.

so

  •  a.__proto.__proto === null
  • Function.__proto__.__proto__  === Object.prototype
  • Object.__proto__.__proto__ === Object.prototype
  • Foo成员寻找比b多了一层Function.prototype


Object.prototype === Object.__proto__.__proto__ === function(){}    (一个没有prototype的匿名函数)


There is a special Function that don't have property of prototype. And this Function is the end of 原型链(prototypeChain), cause its __proto__ === null.

查找

查找对象属性时,根据__proto__一级级向上查找,递归的终点是null,即上面的Object.prototype.__proto.


四 构造函数

what

一个用于new实例对象的函数对象叫作构造函数。

constructor

构造方法,默认位于构造函数上的prototype对象中。因此若是原型改变了,那么该函数的construtor也就改变了。

那么一个构造函数的constructor位于哪里呢,经过追溯,发现他位于Function.prototype上!

Foo = () => {}
Foo.constructor === Function.prototype.constructor
f = new Foo()
f.constructor === Foo.prototype.constructor //证实构造方法位于构造函数的prototype上
f.constructor === Foo.prototype.constructor === Foo // 构造函数即构造方法复制代码

new作了什么

  1. 建立一个空对象(开创空间)
  2. 将父类的prototype赋给空对象的__proto__(原型的继承,共有成员共享,注意这个过程使得对象能够追溯到一个属性:constructor,位于父类的prototype上)
  3. 使用call让空对象执行父类构造函数(建立私有成员,执行构造方法)
  4. 返回这个对象

原来是返回一个追溯第一次指向父类构造函数的原型的对象啊,而且还经过3步骤具有了构造函数中的私有成员建立,经过2共用了父类的共有属性,而且其祖先类的共有属性也能够经过原型链公用了哦。

这就是继承了吗?在业务复杂场景中,不能知足呀,那么让咱们来研究一下继承吧!

五 继承

一个经典的问题,传统的继承使用了原型链,进行类继承,简单罗列下2级目录

  • 类继承
  • 构造函数继承
  • 组合继承
  • 原型式继承
  • 寄生式继承(工厂模式)
  • 寄生组合式继承(比较完美)

寄生组合式继承

function Animal(color) {  this.color = color;  this.name = 'animal';  this.type = ['pig', 'cat'];}Animal.prototype.greet = function(sound) {  console.log(sound);}function Dog(color) {  Animal.apply(this, arguments);  this.name = 'dog';}/* 注意下面两行 */Dog.prototype = Object.create(Animal.prototype);Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() {  console.log(this.name);}
var dog = new Dog('白色');   var dog2 = new Dog('黑色');     
dog.type.push('dog');   console.log(dog.color);   // "白色"console.log(dog.type);   // ["pig", "cat", "dog"]
console.log(dog2.type);  // ["pig", "cat"]console.log(dog2.color);  // "黑色"dog.greet('汪汪');  //  "汪汪"复制代码

圣杯模式

// 最优化 圣杯模式
var inherit = (function(c,p){ // c : target p: origin	var F = function(){};
	return function(c,p){
		F.prototype = p.prototype;
		c.prototype = new F();
		c.uber = p.prototype; // 为了让咱们知道Target真正继承自谁
		c.prototype.constructor = c; // 把Target的构造函数指向归位
	}
})();复制代码

es6 Extends继承

export default class {
  params = {};

  set(key, value) {
    this.params[key] = value;
    return this;
  }
  
  done () {
    const headers = new Header();
    fetch( `/api?params=${encodeURIComponent(JSON.stringify(this.params))}` , {
      method: "GET",
      mode: "cors",
      credentials: "include",
      headers
    });
  }
}

//继承并覆盖done方法
const temp = new class extends access {
    done() {
      const headers = {
        'Accept': 'application:/json',
        'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
      };
      fetch( `/api?params=${encodeURIComponent(JSON.stringify(this.params))}` , {
        method: "GET",
        mode: "cors",
        credentials: "include",
        headers
      });
    }
  }();
复制代码


完美继承(划重点了)

what

讲真的,我以为弄清什么是完美继承才是最难的,由于,网上不少例子只讲了如何实现完美继承,讲了不少继承,像上面同样,可是从实用角度出发, 其实,不少时候,咱们弄明白原理是为了使用甚至创造轮子,可是更多时候从业务出发,咱们是更须要记住场景的对应使用,那么用一句话讲清完美继承的场景就是:
使得子类能够继承父类的共有变量,也能够将父类中的私有变量在本身中独立的建立一份,同时可使自身的原型保持独立性,再添加子类本身的共有变量,丰富本身。

是否是看的有点晕,好的,更加简洁易懂的描述:

1. 使子类继承父类的共有变量(原型属性),使子类继承父类的私有变量(对象成员)

2. 使子类能够丰富自身,保持自身的私有变量和共有变量(原型)与父类间的独立性

是否是写的有点不接地气?好!再用通俗的话(俗称:人话)描述一下:

父类有的子类有(copy了一份),子类有的父类没有。 

你看,是否是就像是父子关系之间的传承,读到这里,脑子里是否是蹦出了两个字,原来这就是——继承!什么叫继承: 完成了从上层的传承后,再保持自身的丰富(是否是很像基因!!)

why

场景的复杂,业务的须要,效率的提升,js世界的设计,面向对象的须要

how

1. 使用对子类prototype赋值父类的实例来实现独立的共有变量(原型)继承,同时因为构造函数位于自身的ptototype上,使用对子类prototype.constructor赋值子类来保持独立性(以便于子类自身原型的丰富)

2. 使用父类构造函数在子类构造函数中第一行执行(因为this指向的不一样,则实现了独立性)实现父类私有变量在子类中的继承

do

实现的话上面已经介绍了,其实上面三种方案里到处充斥着how的执行,就算是语法糖的es6,其extends也就是实现了1,而后还须要在子类中首行执行super(),这不就是在实现2吗!

还不懂? 

extends super到底干了什么?别急,这就为你解析!

extends super 语法糖解析

oneDemo

class People{
constructor(name, age) {
  this.name = name;
  this.age = age;
}
  
say(){
  alert("hello")
}
  
static see(){
  alert("how are you")
}
}

class xiaowang extends People{
  constructor(){
      super()
  }
  say(){alert("not hello , i am KingXiao")} // 丰富
}复制代码

extends

今天有点晚,改天详细,具体原理本身分析就是建立一个自执行函数,将父类穿进去,而后将父类Create一份将实例赋值给子类的原型,而后再将子类原型上的构造函数属性赋值为子类。作完这系列操做后返回子类遇到了calss,而后就是子类自身的丰富了。(父类原型方法继承

super

super作了什么呢,其实就是一句es5的语法糖,People.call(this)     父类对象方法继承

看到这里,是否是对于js的继承,甚至于面向对象中的继承有了很是透彻的理解了呢,愣着干吗?点赞呀!哈哈0-0妈妈不再怕我不会写代码了

六 js内置类型构建过程

学习自 木易杨说进阶探究Function&Object鸡蛋问题

JavaScript 内置类型是浏览器内核自带的,浏览器底层对 JavaScript 的实现基于 C/C++,那么浏览器在初始化 JavaScript 环境时都发生了什么?

没找到官方文档,下文参考自 segmentfault.com/a/119000000…

一、用 C/C++ 构造内部数据结构建立一个 OP 即 (Object.prototype) 以及初始化其内部属性但不包括行为。

二、用 C/C++ 构造内部数据结构建立一个 FP 即 (Function.prototype) 以及初始化其内部属性但不包括行为。

三、将 FP 的 [[Prototype]] 指向 OP。(Function.prototype.___proto__ = Object.prototype)

四、用 C/C++ 构造内部数据结构建立各类内置引用类型。

五、将各内置引用类型的[[Prototype]]指向 FP。(Array.__proto__ = Function.prototype)

六、将 Function 的 prototype 指向 FP。(FP = Function.ptototype)

七、将 Object 的 prototype 指向 OP。(OP = Object.prototype)

八、用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。(行为挂载)

九、用 Object 实例化出除 Object 以及 Function 的其余内置引用类型的 prototype 属性对象。(Array.prototype = new Object() ...)

十、用 Function 实例化出除Object 以及 Function 的其余内置引用类型的 prototype 属性对象的行为并挂载。(行为挂载)

十一、实例化内置对象 Math 以及 Grobal

至此,全部内置类型构建完成。



引用

本文中引用了一下连接中部份内容做为参考

ghmagical.com/article/pag… © ghmagical.com

www.cnblogs.com/diligenceda…

juejin.im/post/5c64d1…

blog.csdn.net/qq_29590623…

segmentfault.com/a/119000001…

写在最后

一遍熬夜一边查写一遍理解写出本身的想法,有的可能错了,欢迎指出!

若是对你有帮助,能点个赞那就太感谢了!^_^

tot我写的真的不好吗,为何阅读量进百么有一个点赞也没有一个评论TOT。