JS 原型的妙用

实例和对象的区别

JS语法没有类这个概念(当然ES6引用了类这个概念)。只能通过构造函数来创建类,new一个类之后就是对象。

JS是面向对象的语言,一切都是对象。那么函数也是对象、实例也是对象、对象字面量也是对象,而且JS中所有对象的成员都是公用的。

即:对象是一个具有多种属性的内容结构!所以:实例都是对象,而对象不全是实例!

对象和函数

函数就是对象;对象不一定是函数,而且在JS中,函数还是第一类对象,而且还是也是实例。

var add = new Function('a','b','return a+b');
//add为一个函数,它也是构造函数Function的一个实例,即对象
add(1,2) //

结论:一切皆为对象,函数为第一类对象。而对象不是实例!

什么是继承

继承就是子类化,从一个基础或者超类对象中继承相关的属性

JS继承的方式

JS继承主要有两种、apply(call)和 prototype基础,很多继承的设计模式都是按照这两个继承来设计的。

1.先看apply(call)的一个例子

function Stream(x, y){
this.x = x;
this.y = y;
//私有变量
var _x = 9;
//暴露子类对私有变量的访问
this.getX = function(){
//_x:9
console.log("_x:"+_x);
return _x;
}
}
var stream = function(){
var array = Array.prototype.slice.call(arguments);
//继承Stream中的公有属性。array是对公有属性赋值。
Stream.apply(this,array);
}
var str = new stream(7, 8);
//x:7
console.log("x:" + str.x);
//str:9
console.log("str:" + str.getX());

2.另外一个是原型继承,通过它可以实现类和实例直接的继承关系。

2.1 实现数据备份

// 通过原型来来实现数据备份
function p(x){
this.x = x;
}
p.prototype.backup = function(){ //备份函数、初始化第一个对象时进行备份,还原也只能还原第一个对象的数据
for(i in this){
p.prototype[i] = this[i];
}
}
var p1 = new p(1);
console.log(p1.x); //1;
p1.backup(); //p1的原型对象(构造函数、构造类)的原型属性应用了p1对象。即p.prototype.x = p1.x;
p1.x = 10;
p1 = p.prototype; //p1对象赋值给p.prototype,p1.x = p.prototype.x 实现对象的备份还原。
console.log(p1.x);

2.2  继承封装

   这也是原型最重要的特征,下面是套路,这是一种原型设计模式,需要牢记!

  extend 方法

function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype; //意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
}
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

method方法

这个例子是来源于《JS模式》这本书

if(!Function.prototype.method){
Function.prototype.method = function(name,fn){
//this是调用method的构造函数
this.prototype[name] = fn;
return this;
}
}
var Person = function(name){
this.name = name;
}.method("setName",function(name){
//this是构造函数的实例(new方法)
this.name = name;
return this;
}).method("getName",function(){
console.log(this.name);
return this;
});
var p = new Person("anthonyliu");
//anthonyliu
p.getName();
p.setName("liuyinlei").getName();
//liuyinlei

 __proto__和prototype

对象不一定有prototype属性,例如{};构造函数肯定有prototype属性,而且构造函数定义的实例会有__proto__属性,

该属性值是构造构造函数的prototype指向的对象,如下:

function A(x,y){
this.x = x;
this.y = y;
}
A.prototype.getX = function(){
return this.x;
}
var a = new A(3,4);
console.log(a.__proto__.constructor); //A
//即:a.__proto__.constructor.prototype = A.prototype;A.prototype.construct = a.__proto__.construct = A

还有更诡异的事情:一个函数的静态成员对象的__proto__赋值一个对象b,那个可以直接通过静态成员访问b对象中的成员:例如:

var request = {
x:"100"
};
function A() {
// body...
}
A.request = {
__proto__:request
};
console.log(A.request.x); //100

express源码中也能看到其中的身影:

function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
//继承事件的方法和proto的方法。
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
//app.request为req(node的http.IncomingMessage 类的一个实例)
app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };
app.init();
return app;
}

其实__proto__属性可以看做是一个mixin方法:

var proto ={};
var mixin = {"x":"y"};
proto.__proto__ = mixin;
console.log(proto.x); //y

console打印原型

var A = function (argument) {
// body...
};
A.prototype.pipe = function(){ };
//A { pipe: [Function] }
console.log(A.prototype);
//打印nodejs中的stream.prototype
var stream = require("stream");
//Stream { pipe: [Function] }
console.log(stream.prototype);

打印函数

function B(x,y){
this.x = x;
this.y = y;
}; var A = function (x,y) {
B.apply(this,arguments);
// body..
};
A.prototype.pipe = function(){ };
A.prototype.pipe2 = function(){ }
A.getA =function(){ }
A.getB = function(){ }
//{ [Function: A]
// getA: [Function],
// getB: [Function]
// }
console.log(A); 

mixin设计模式

可以将继承MiXin看作为一种通过扩展收集功能的方式

e.mixin = function(t) {
for (var i in e.prototype)
t[i] = e.prototype[i]; //调用该模块,t继承了e.prototype的方法,
return t
}
//e.prototype写法是:
function e() {}
var j = e.prototype;
return j.on = function(e, t) {},
j.emit =function(e,t){},
j //引用,e.prototype中的方法都是j的方法,即实现了继承。

下面是一个简单的mixin模式写法

function A(x){
this.x = x;
}
A.prototype.setA = function(){console.log("coming in a");}
A.prototype.setB = function(){console.log("coming in b");}
A.mixin = function(t){
for(key in A.prototype){
t[key] = A.prototype[key];
}
}
var b= {};
A.mixin(b);
b.setA(); //coming in a

小结:mixin设计模式非常像拷贝继承,简单说,把父对象的所有属性和方法,拷贝进子对象,扩展方法,都是从父对象来扩展方法。

在nodejs中util包有个函数inherits,也具有mixin的功能。建议用ES6 的 class 和 extends 关键词获得语言层面的继承支持,记住下面的套路:

const util = require('util');
const EventEmitter = require('events'); function MyStream() {
EventEmitter.call(this);
} util.inherits(MyStream, EventEmitter); MyStream.prototype.write = function(data) {
this.emit('data', data);
}; const stream = new MyStream(); console.log(stream instanceof EventEmitter); // true
console.log(MyStream.super_ === EventEmitter); // true stream.on('data', (data) => {
console.log(`接收的数据:"${data}"`);
});
stream.write('运作良好!'); // 接收的数据:"运作良好!"
上一篇:mpvue——引入echarts图表


下一篇:CSharp设计模式读书笔记(20):观察者模式(学习难度:★★★☆☆,使用频率:★★★★★)