updateTime: 2019-4-11 01:15
updateContent: 继承的实现,优化部分内容细节
updateTime: 2019-4-15 00:11
updateContent: 解析完美继承,es6继承内部一探
updateTime: 2019-4-17
updateContent: js内置类型构建过程
一 前言
读了本文后,你会对js中的属性追溯机制,继承原理,构造函数和类,万物皆对象,有一定深度理解。
为什么要读这些? 为了提高代码的复用,js中的继承,函数封装,复制extends,混入mixin,借用call/apply,都是为了代码的复用,为了更高效和更省时的编程!一个好的FEer必须跨过这些!
二 原型 prototype
what
js在es6之前中没有类的关键词,在一些需要进行面向对象编码场景中通过js中一种引用数据类型Function(函数类型)来实现类。原型,英文名prototype,通常出现在一个函数类型的属性中,看遍六大基本类型,三大引用类型,唯有函数类型天生拥有属性prototype。这个属性被称为原型。
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做了什么
- 创建一个空对象(开创空间)
- 将父类的prototype赋给空对象的__proto__(原型的继承,共有成员共享,注意这个过程使得对象可以追溯到一个属性:constructor,位于父类的prototype上)
- 使用call让空对象执行父类构造函数(创建私有成员,执行构造方法)
- 返回这个对象
原来是返回一个追溯第一次指向父类构造函数的原型的对象啊,并且还通过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…
1、用 C/C++ 构造内部数据结构创建一个 OP 即 (Object.prototype) 以及初始化其内部属性但不包括行为。
2、用 C/C++ 构造内部数据结构创建一个 FP 即 (Function.prototype) 以及初始化其内部属性但不包括行为。
3、将 FP 的 [[Prototype]]
指向 OP。(Function.prototype.___proto__ = Object.prototype)
4、用 C/C++ 构造内部数据结构创建各种内置引用类型。
5、将各内置引用类型的[[Prototype]]指向 FP。(Array.__proto__ = Function.prototype)
6、将 Function 的 prototype 指向 FP。(FP = Function.ptototype)
7、将 Object 的 prototype 指向 OP。(OP = Object.prototype)
8、用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。(行为挂载)
9、用 Object 实例化出除 Object 以及 Function 的其他内置引用类型的 prototype 属性对象。(Array.prototype = new Object() ...)
10、用 Function 实例化出除Object 以及 Function 的其他内置引用类型的 prototype 属性对象的行为并挂载。(行为挂载)
11、实例化内置对象 Math 以及 Grobal
至此,所有内置类型构建完成。
引用
本文中引用了一下链接中部分内容作为参考
ghmagical.com/article/pag… © ghmagical.com
写在最后
一遍熬夜一边查写一遍理解写出自己的想法,有的可能错了,欢迎指出!
如果对你有帮助,能点个赞那就太感谢了!^_^
tot我写的真的很差吗,为什么阅读量进百么有一个点赞也没有一个评论TOT。