继承小结
实例和对象的区别
JS语法没有类这个概念(当然ES6引用了类这个概念)。只能通过构造函数来创建类,new一个类之后就是对象。
一切都是对象。那么函数也是对象、实例也是对象、对象字面量也是对象,而且JS中所有对象的成员都是公用的。
即:对象是一个具有多种属性的内容结构!所以:实例都是对象,而对象不全是实例!
对象和函数
函数就是对象;对象不一定是函数,而且在JS中,函数还是第一类对象,而且还是也是实例。
var add = new Function('a','b','return a+b'); //add为一个函数,它也是构造函数Function的一个实例,即对象 add(1,2) //3
结论:一切皆为对象,函数为第一类对象。而对象不是实例!
什么是继承
继承就是子类化,从一个基础或者超类对象中继承相关的属性
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('运作良好!'); // 接收的数据:"运作良好!"