创建对象
var a = function(){
return {
b: function(){console.log('this is b in a');},
c: function(){}
}
}
var k = a();
k.b();
//上面这个方法虽然可以调用,但创建的对象k和对象a没有关系(返回出来的对象本身和a对象无关)
//所以可以用类的方法来创建,如下(需要new)
var a2 = function(){
this.b = function(){console.log('this is b in a2');}
this.c = function(){}
}
var k2 = new a2();
k2.b();
//每次new新对象时,都会复制一套this的属性,这样每个方法都会复制一遍,浪费大
//可以用原型,如下
var a3 = function(){}
a3.prototype = {
b: function(){console.log('this is b in a3');},
c: function(){}
}
var k3 = new a3();
k3.b();
//在b、c最后return this,可实现链式调用
//扩展原生对象(Function,Array等)
Function.prototype.b = function(){console.log('this is b in Function');}
var f = function(){}; //或var f = new Function()
f.b();
//但这样会污染原生对象,别人创建的函数也会被自己创建的函数污染
//可以抽象出一个统一添加方法的功能方法
Function.prototype.addMethod = function(name, fn){
this[name] = fn;
return this;
}
//然后通过其来添加方法
var f2 = function(){}; //或var f2 = new Function(){};
f2.addMethod('b', function(){console.log('this is b in Function by add');return this;})
.addMethod('c', function(){console.log('this is c in Function by add');return this;})
f2.b().c();
//用上面的方法,只会给f2添加b方法,如果另外创建f3也不会有b方法
//下面f3的b方法是Function.prototype.b中扩展的,如果没有这句
//由于addMethod不会污染f3,下面运行会报错
var f3=function(){}
f3.b();
//上面是函数式编程的写法,下面是类式调用的写法
Function.prototype.addMethod = function(name, fn){
this.prototype[name] = fn; //这里不同
return this;
}
var f4 = function(){}; //或var f4 = new Function(){};
f4.addMethod('b', function(){console.log('this is b in Function by add for new');return this;})
.addMethod('c', function(){console.log('this is c in Function by add for new');return this;})
var ff4 = new f4(); //因为涉及到原型链,需要new
ff4.b().c();
面向对象
下面介绍了封装、继承、多态
//封装
var man = function (name) {
this.name = name; //this是函数内部自带的变量,指向当前对象,在new中的构造函数中需要用this
};
man.prototype = {
getName: function () {
console.log(this.name);
},
};
var bob = new man("Bob");
bob.getName();
console.log(bob.name);
//this的属性在每次new时都会得到创建,而prototype中的方法和属性不会再次创建,而是通过指向访问到
//prototype对象会像函数中创建this一样创建一个constructor属性,该属性指向拥有整个原型对象的属性或对象,如上面就是指向man
//new出的对象(如这里的bob),也会有个属性__proto__,指向类的原型所指向的对象(这里是man.prototype)
var man2 = function (age) {
var myAge = age; //没有this的为私有属性,外界访问不到
//这里方法没放在prototype中,因为原型中也无法访问到私有属性
//只有在里面可以访问,这种方法称为特权方法
this.getAge = function () {
console.log(myAge);
};
};
//new时是对this的不断赋值,在外面用点语法增加的属性不会加到new出的新对象里
//prototype可以是因为新对象的__proto__和类的prototype指向同一对象
//比如下面的k只能用man2.k访问,在bob2中不存在
man2.k = true;
var bob2 = new man2(18);
bob2.getAge();
console.log(bob2.k); //undefined
console.log(man2.k); //true
//闭包
var man3 = (function () {
var sex = 0; //闭包中的sex因为不在返回的函数中,无论是否有new外界无法访问(静态私有变量)
//下面的和普通构造函数相同,也可以把prototype写闭包外面,即man3.prototype
//但写在里面更像一个整体
function MAN(age) {
var k = 1;
this.age = age;
}
MAN.prototype = {};
return MAN;
})();
//如果使用一个构造函数时忘记new,而是直接赋值使用,里面的this会指向window,这时将取不到里面的属性
//可以用以下安全模式
var man4 = function (a) {
if (this instanceof man4) {
this.a = a;
} else {
return new man4(a);
}
};
var m4 = man4(1); //如果没有上面的安全模式,这里不new将导致m4.a没有值,值在window.a
//继承
//类式继承
function a() {
this.a = 1;
this.b = [1, 2];
}
a.prototype = {}; //为父元素添加共有方法
function b() {}
b.prototype = new a();
b.prototype.constructor = b;
var kk1 = new b();
var kk2 = new b();
kk1.a = 2;
kk1.b.push(3);
console.log("kk1.a: " + kk1.a); //2
console.log("kk1.b: " + kk1.b); //1,2,3
console.log("kk2.a: " + kk2.a); //1
console.log("kk2.b: " + kk2.b); //1,2,3
//但这种方法里如果有引用类型,改变时会相互影响;且在使用时无法向父类传递参数
//为解决,可以使用构造函数继承
function a2(num) {
this.a = [1, 2, num];
}
function b2(num) {
a2.call(this, num);
}
var kkk1 = new b2(3);
var kkk2 = new b2(3);
kkk1.a.push(4);
console.log("kkk1.a: " + kkk1.a); //1,2,3,4
console.log("kkk2.a: " + kkk2.a); //1,2,3
//这样引用类型就不会互相影响,但没有prototype,此时父类的原型不会被子类继承,如果放在构造函数中,不符合代码复用原则
//所以出现了组合继承
function a3(index) {
this.arr = [1, 2, index];
console.log("看看运行了几次——组合继承");
}
a3.prototype = {};
function b3(index) {
a3.call(this, index);
}
b3.prototype = new a3(); //类式继承
b3.prototype.constructor = b3;
b3.prototype.funA = function () {};
var kkkk1 = new b3(6);
var kkkk2 = new b3(7);
kkkk1.arr.push("x");
console.log(kkkk1.arr); //1,2,6,x
console.log(kkkk2.arr); //1,2,7
//这时引用类型不会相互影响,也可以使用父类的原型方法
//但我们会发现,'看看运行了几次'输出了3次,分别是call和两次new a3(),造成资源浪费
//于是有下面的另一种继承方式
//原型式继承
function inheritObject(o) {
function F() {} //过渡函数对象
F.prototype = o;
return new F();
}
var book = {
k: [1, 2, 3],
};
var book1 = inheritObject(book);
var book2 = inheritObject(book);
//上面相当于对类式继承的封装,也会有类式继承的问题:引用类型相互影响
//在此基础上,产生了寄生式继承
function createBook(obj) {
var o = new inheritObject(obj); //原型继承创建对象
o.getName = function () {}; //扩展新对象
return o;
}
//这是对原型继承的二次封装,在其中对继承的对象进行扩展
//在这个思想上出现了最终的寄生组合式继承
function inheritPrototype(a, b) {
//a子类,b父类
//复制一份父类的原型副本保存在变量
var p = inheritObject(b.prototype);
//修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = a;
//设置子类原型
a.prototype = p;
}
//因为父类的构造函数在构造函数继承时就已经获取,所以这里不再获取
//而只获取父类的原型
function fa(name) {
this.c = [1, 2, name];
console.log("看看运行了几次——寄生组合式继承");
}
fa.prototype = {};
function ch(name) {
fa.call(this, name);
}
inheritPrototype(ch, fa);
ch.prototype.aa = function () {};
var in1 = new ch(1);
var in2 = new ch(2);
//这样引用类型不会相互影响,父类也只调用时运行了两次
//但有个缺陷是给子类添加原型方法只能通过点语法ch.prototype.xx一个个添加,直接用赋值对象的方法会覆盖掉从父类原型继承的对象
//多继承
//由于js的继承是通过原型链实现的,但原型链只有一条,理论上无法实现对多个父类的继承
//但可以通过其他方法继承多个对象的属性来实现类似多继承
//这里把函数绑定到原生对象上,这样对象也就有这一方法
Object.prototype.mix = function () {
var i = 0,
len = arguments.length,
arg;
for (; i < len; i++) {
//缓存当前对象
arg = arguments[i];
for (var property in arg) {
this[property] = arg[property];
}
}
};
var sa = {},
sb = {},
sc = {};
sa.mix(sb, sc);
//多态
//普通写法就是感觉参数switch,下面是面向对象的类形式的写法
function Add() {
function a() {}
function b() {}
this.add = function () {
var arg = arguments;
var len = arg.length;
switch (len) {
case 0:
return a();
case 1:
return b(1);
}
};
}
var A = new Add();
A.add(1, 2);
再谈寄生组合式继承
上面寄生组合式基础看的还是比较懵,这里再贴上《javascript 高级程序设计》的实现
其实和上面的差不多,主要说明文字不太一样
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//上面的函数得到的对象F,拥有了对象o的全部属性(在原型链上),而修改F的属性,不会影响到o,相当于把o复制了一份。
//使用object方法,就可以将Super.prototype的属性「复制」到Sub.prototype上了,当然这儿还需要修正一下constructor的指向。
function inheritPrototype(subType, superType){
var o = object(superType.prototype); //创建对象
o.constructor = subType; //增强对象
subType.prototype = o; //指定对象
}
/*
这个示例中的 inheritPrototype()函数实现了寄生组合式继承的最简单形式。
这个函数接收两个参数:子类型构造函数和超类型构造函数。
在函数内部,第一步是创建超类型原型的一个副本。
第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。
最后一步,将新创建的对象(即副本)赋值给子类型的原型。
这样,我们就可以用调用 inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句了
*/
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
var k = new SubType();
为什么要改变 constructor 的指向
下面是一个使用 prototype 模式的继承
如果”猫”的 prototype 对象,指向一个 Animal 的实例,那么所有”猫”的实例,就能继承 Animal 了。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
代码的第一行,我们将 Cat 的 prototype 对象指向一个 Animal 的实例。
它相当于完全删除了 prototype 对象原先的值,然后赋予一个新值。
关键在第二行
原来,任何一个 prototype 对象都有一个 constructor 属性,指向它的构造函数。如果没有”Cat.prototype = new Animal();”这一行,Cat.prototype.constructor 是指向 Cat 的;加了这一行以后,Cat.prototype.constructor 指向 Animal。
alert(Cat.prototype.constructor == Animal); //true
更重要的是,每一个实例也有一个 constructor 属性,默认调用 prototype 对象的 constructor 属性。
alert(cat1.constructor == Cat.prototype.constructor); // true
因此,在运行”Cat.prototype = new Animal();”这一行之后,cat1.constructor 也指向 Animal!
alert(cat1.constructor == Animal); // true
这显然会导致继承链的紊乱(cat1 明明是用构造函数 Cat 生成的),因此我们必须手动纠正,将 Cat.prototype 对象的 constructor 值改为 Cat。这就是第二行的意思。
这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了 prototype 对象,
o.prototype = {};
那么,下一步必然是为新的 prototype 对象加上 constructor 属性,并将这个属性指回原来的构造函数。
o.prototype.constructor = o;
林秀栋的技术博客