关于js对象和原型继承
内容提要
- 面向对象特征回顾;
- 为什要设计
prototype: js历史; - 栗子: 原型、构造函数、实例化机制、原型链;
new Fn()的实现原理;- Object内置属性和函数整理、class语法糖
- 综合实战小练习;
面向对象的三个基本特征是:封装、继承、多态。

对于有基于类的语言经验 (如 Java 或 C++) 的开发人员来说,JavaScript 有点令人困惑,因为它是动态的,并且本身不提供一个class实现相关类的继承。全靠一种很奇特的"原型链"(
prototype chain)模式,来实现类的继承扩展。(在 ES2015/ES6 中引入了class关键字,但只是语法糖,JavaScript 仍然是基于原型的)。
Javascript继承机制和prototype设计
历史说起 http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
- 1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。急需一种网页脚本语言,使得浏览器可以与网页互动。
- 工程师
Brendan Eich负责开发这种新语言。他觉得,没必要设计得很复杂:Javascript里面所有的数据类型都是对象(object),这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢————Javascript里面都是对象,必须有一种机制,将所有对象联系起来。 - 他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。然后引入了轻量级self语言类似的原型继承,然后14天憋出了javascript这门伟大的语言。
最终js面向对象的设计
1.引入new 从构造函数constructor(相当于类class)到实例
function GirlFriend (name) {
// this.gender = '女';
this.name = name;
}
var lujisGirl = new GirlFriend('庄颜'); // 《三体》罗辑的梦中女神
var jdsGirl = new GirlFriend('奶茶');
2.prototype属性的引入
构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。无法做到数据共享,也是极大的资源浪费。
比如:女神都有的特点——美、体贴、善良的女性...
function GirlFriend (name) {
// this.gender = '女';
this.name = name;
}
GirlFriend.prototype.gender = '女';
var lujisGirl = new GirlFriend('庄颜');
var jdsGirl = new GirlFriend('奶茶');
console.log(lujisGirl.gender, jdsGirl.gender); // '女' '女'
GirlFriend.prototype.gender = '男'; // 假设
console.log(lujisGirl.gender, jdsGirl.gender); // '男' '男'

实例对象共享构造函数的同一个
prototype对象, 这样便实现了"继承"。
原型
1.在JavaScript中创建函数时,JavaScript引擎会向函数添加prototype属性。此prototype属性是一个对象(称为原型对象),默认情况下具有构造函数属性。构造函数属性指向原型对象是属性的函数。
Fn.prototype.constructor === Fn
2.JavaScript创建实例对象时(new constructor(), 赋值创建, Object.create()除外),JavaScript引擎会将__proto__属性添加到新创建的对象中,该对象称为dunder proto(魔术__proto__ (php/python有此概念))。__proto__指向构造函数的原型对象。
obj.__proto__ === Fn.prototype
原型链查找
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop))
return obj[prop]
else if (obj.__proto__ !== null)
return getProperty(obj.__proto__, prop)
else
return undefined
}
回到myGirl栗子:lujisGirl(实例) --> GirlFriend.prototype --> Object.prototype --> null
结论:

-
谈到继承时,JavaScript只有一种结构:对象(实例对象, 构造函数(函数也是对象))。
-
实例对象只有
__proto__属性, 函数对象既有prototype对象, 也有__proto__属性;prototype对象有constructor和__proto__属性。 -
实例对象(obj )的
__proto__指向它构造函数的原型对象(prototype)。该原型对象(prototype)也有一个自己的原型属性__proto__ ,层层向上直到一个对象(Object.__proto__)的原型对象为null。根据定义,null没有原型,并作为这个原型链中的最后一个环节。
Object.prototype.__proto__ === null
new背后的故事
// 伪代码
function new(GirlFriend(args)) {
var obj = {}; // 创建空对象
obj.__proto__ = GirlFriend.prototype; // 建立原型链[继承]
var result = GirlFriend.call(obj, args); // 构造函数执行,将this-> obj, 非继承属性 this.name执行
return typeof result === 'object'? result : obj; // 判断构造函数return: 无返回值 或者 返回一个非对象值
}
stackoverflow上一个人的回答来:
In JavaScript, most functions are both callable and instantiable: they have both a [[Call]] and [[Construct]] internal methods.
在JS中,绝大多数的函数都是既可以调用也可以实例化的.我们既可以直接执行函数得到函数的返回值.也可以通过new操作符得到一个对象.
JavaScript 世界万物诞生记

要点概括:
-
无:
null -
无中生有: null --> No.1_obj (
Object.prototype); -
顶层机器:Object() + 模板对象 => 批量新对象: new Object({a: 12})
继承自
Object.prototype(No.1) -
二级机器:分化的构造函数:
String、Number、Boolean、Array、Date、Error..., 在No.2基础上+新功能继承自No.2 [No.2是谁造出来?]
-
缺少一台制造机器的机器: Function诞生,生产No.2
-
所有上层机器(构造函数)都是Function创造
Object作为一个机器可以看做是有由Function制造出来的,而Function作为一个对象可以看做是由Object制造出来的。
搞懂下边一张经典图,便搞懂js对象世界

// 看成对象
Object instanceof Function;
Function instanceof Function;
Object instanceof Object;
Function instanceof Object;
Object.__proto__ === Function.prototype;
Function.__proto__ === Function.prototype;
Object.__proto__.__proto__ === Object.prototype;
Function.__proto__.__proto__ === Object.prototype;
// 到顶级
Object.__proto__.__proto__.__proto__
=== Function.__proto__.__proto__.__proto__
=== null;
js对象的类型判断
typeof sth;obj instanceof constructor; // 构造函数实例Object.prototype.toString.call(obj)// 最靠谱
export function type(obj) {
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
return map[toString.call(obj)];
}
HTML元素的prototype
HTMLDivElement.prototype
|
|.__proto__
|
HTMLElement.prototype
|
|.__proto__
|
Element.prototype
|
|.__proto__
|
Node.prototype
|
|.__proto__
|
EventTarget.prototype
|
|.__proto__
|
Function.prototype
|
|.__proto__
|
Object.prototype
|
|.__proto__
|
null
js属性方法整理,class语法糖(es6+)
首先问自己:静态属性、原型属性、实例方法、构造函数、继承/扩展

最后的小实战
function Foo(){
getName = function(){
console.log(1)
}
return this;
}
Foo.getName = function(){
console.log(2)
}
Foo.prototype.getName = function(){
console.log(3)
}
var getName = function(){
console.log(4)
}
function getName(){
console.log(5)
}
// pls ouput the results:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
参考文献
-
https://hackernoon.com/prototypes-in-javascript-5bba2990e04b
-
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
-
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype
-
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes
-
https://github.com/mqyqingfeng/Blog/issues/2
-
http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
-
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
-
https://medium.com/@peterchang_82818/javascripter-%E5%BF%85%E9%A0%88%E7%9F%A5%E9%81%93%E7%9A%84%E7%B9%BC%E6%89%BF%E5%9B%A0%E5%AD%90-prototype-prototype-proto-object-class-inheritace-nodejs-%E7%89%A9%E4%BB%B6-%E7%B9%BC%E6%89%BF-54102240a8b4
-
http://www.ituring.com.cn/article/56184

原型prototype的修改与重写(覆盖)的区别
先创建对象,然后再修改原型:覆盖后,已经创建的对象无法访问到修改后的原型