原型是个玄学,学了好久的js都没有搞清楚究竟是个啥,老是只知其一;不知其二,今天系统的总结下(按我的理解),欢迎批评指正。
说到原型,那么咱们搞清楚这些名词先:构造函数、原型、实例、__proto__、new操做等。html
// 构造函数
function Foo(name) {
// 私有属性
var age = 1
// 公有属性
this.name = name
}
// 原型上的属性
Foo.prototype.getName = function() {
return this.name
}
// 静态属性
Foo.id = 123
// 实例foo
var foo = new Foo('Tom')
foo.name // Tom
foo.age // undefined
foo.getName() // Tom
Foo.id // 123
复制代码
问:为何foo能访问到name、getName, 访问不到age?
答:es6
// 模拟 new 操做
function myNew(Foo){
var obj = {}
// 解释为何能访问 getName
obj.__proto__ = Foo.prototype
// 将构造函数里的公有属性,强制绑定到obj, 解释问什么能访问 name
Foo.call(obj)
return obj
}
复制代码
其基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法。设计模式
实例 foo 能访问到构造函数 Foo 里的公有属性和原型上的属性, 实例 parent 能访问到构造函数 Parent 里的公有属性和原型上的属性, 将构造函数Foo的原型指向实例parent,则 foo 也能够访问 parent 所能访问的内容, 从而实现继承。bash
function Parent(){
this.aaa = 'aaa'
this.books = ['1', '2']
}
Parent.prototype.getAAA = function(){
return this.aaa
}
// 这个时候会覆盖以前的 Foo.prototype.getName, 解决:子类原型上自定义的方法后移
Foo.prototype = new Parent()
Foo.prototype.getName = function() {
return this.name
}
var foo = new Foo('Tom')
foo.getAAA() // 'aaa'
复制代码
原型关系:app
var foo1 = new Foo('Tom')
var foo2 = new Foo('Tom1')
foo1.books.push('3')
foo2.books // ['1','2','3']
复制代码
解决父类Parent里的公有引用数据类型属性,会互相影响
缺点:没法访问Parent.prototype上的内容。函数
function Parent(name){
this.books = ['1','2']
}
function Foo(name) {
Parent.call(this,name)
}
var foo1 = new Foo('Tom1')
var foo2 = new Foo('Tom2')
foo1.books.push('3')
foo2.books // ['1','2']
复制代码
Parent里的公有引用数据类型属性互不影响,也可访问Parent.prototype上的内容。
缺点:要调用两次父类构造函数,而且books会存在于foo和Foo.prototype上性能
function Parent(){
this.books = ['1','2']
}
function Foo() {
Parent.call(this) // 第二次调用 new Foo() 时。
}
// 子类的原型指向父类的实例
Foo.prototype = new Parent() // 第一次调用。
var foo = new Foo()
复制代码
原型关系: ui
借助第三方构造函数F实现继承, 本质上是经过__proto__牵桥搭线。this
function inherit(o) {
function F(){}
F.prototype = o
return new F()
}
obj = {
age: 10
}
me = inhreit(obj) // me.__proto__指向F.prototype也就是o
// 等同于
const me = Object.create(obj);
// Object.create原理
const me = Object.create(obj); ===> me.__proto__ = obj
复制代码
经过借用构造函数来继承属性(call),经过原型链来继承方法。相比较组合继承,则其基本思路是:没必要为了指定子类的原型而调用父类的构造函数(避免调用两次父类构造函数),而是将父类原型的副本放到子类原型上。es5
function inherit(o) {
function F(){}
F.prototype = o
return new F()
}
function inheritPrototype(Child, Parent) {
var p = inherit(Parent.prototype) // p.__proto__ = Parent.prototype
p.constructor = Child
Child.prototype = p
}
function Parent(age){
this.age = age
}
function Child(name, age) {
this.name = name
Parent.call(this, age)
}
inheritPrototype(Child, Parent)
// 为避免被覆盖,定义子类原型上的方法,要写在 inheritPrototype 以后
Child.prototype.getAge = function() {
return this.age
}
var child = new Child('Tom', 12)
child.getAge() // 12
复制代码
以上全部继承方式,原型上的引用数据类型被更改时会互相影响。 使用约定 --- 通常原型上只用来存方法,而不存数据,来规避。
// es6
class Point {
// 静态属性
static id = 1
// 私有属性,约定用_加以区分,但实例仍然能够访问
_count = 1
// 原型上的方法
constructor() {
// 公有属性
this.x = 1
}
toString() {
// ...
}
toValue() {
// ...
}
}
const point = new Point()
point.hasOwnProperty('x') // true
point.hasOwnProperty('_count') // true
// class里的方法 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
// es5 的方法
function Point() {
this.toValue = function() {}
}
// toValue方法在实例 point 上,而不是 Point.prototype 上。
var point = new Point()
复制代码
相比于es5, es6多作了这步操做Child.__proto__ = Parent(子类的__proto__指向父类)。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
Bar.__proto__ === Foo // true
复制代码
A.prototype.constructor.call(this)
(A是父类),ES5 的继承,实质是先创造子类的实例对象this(肯定this指向),而后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制彻底不一样,实质是先将父类实例对象的属性和方法,加到this上面(因此必须先调用super方法),而后再用子类的构造函数修改this(肯定this指向)。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
// 等同于
class A {
}
class B {
}
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype = Object.create(A.prototype)
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
// 等同于
B = Object.create(A)
const b = new B();
// ------------------------------------------------------
// setPrototypeOf 原理
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
复制代码