oo是javascript的重点也是难点,这一章我看了好几遍了。个人觉得这章理解起来不是特别容易,所以有些笔记我决定按照自己的理解以及网上一些前辈写的比较好的文章来写,如有不正确望斧正。后续可能会对内容更新。
OOP 定义:使用对象时,只关注对象提供的功能,不关注内部细节,更简洁说是,不了解原理的情况下,会使用其功能。
对象 定义:“黑盒子”,不了解内部结构,知道表面的各种操作。
6.1创建对象
var person = new Object(); person.name = "flyer"; person.age = "26"; person.job = "WED"; person.sayName = function(){ alert(this.name); } person.sayName();
缺点:使用同一个接口创建很多对象,会产生大量重复的代码。
6.1.1工厂模式
function createPerson(name,age,job){ // 原料 var o = new Object(); // 加工 o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; // 出厂 return o; } var person1 = createPerson("flyer",26,"WED"); var person2 = createPerson("bird",27,"UE"); person1.sayName();// flyer person2.sayName();// bird
优点:抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。
缺点:解决了创建多个相似对象的问题,但没有解决对象识别的问题。
没有new。
函数重复,资源浪费。
6.1.2 构造函数模式
function Person(name, age, job){ //构造函数以一个大写字母开头 /* 系统会做的事情: * var this = new Object(); 声明的this是新的空白对象 * */ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; // 系统会替我们返回 return this } var person1 = new Person("flyer",26,"WED"); //当在函数加new时,系统会做一些事情 var person2 = new Person("bird",27,"UE"); person1.sayName(); //"flyer" person2.sayName(); //"bird" alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true /* * 对象的constructor属性最初是用来标识对象类型的,检测类型用instanceof操作符更可靠。 * person1和person2对象既是Object的实例,也是Person的实例。 */ alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true alert(person1.sayName == person2.sayName); //false
构造函数与其他函数的唯一区别,就是调用方式不同。任何函数,只要通过new操作符来调用,就是构造函数。
缺点:每个方法都要在每个实例上重新创建一遍。
在全局作用域中定义的函数实际上只能被某个对象引用,如果对象定义很多方法,那么就要定义很多个全局函数,我们定义的这个引用类型没有封装性可言。
6.1.3 原型模式
定义:用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。也就是通过调用构造函数而创建的那个对象的原型对象。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象信息,而是可以将这些信息直接添加到原型对象中。
function Person(){ } Person.prototype.name = "flyer"; Person.prototype.age = 26; Person.prototype.job = "WED"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); // "flyer" var person2 = new Person(); person2.sayName(); // "flyer" alert(person1.sayName == person2.sayName); // true
1.理解原型
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性。在默认的情况下,所有prototype属性都会自动获得一个constructor属性,这个属性包含一个指向prototype属性函数的指针。
个人理解原型就是类似于css中的class。
用构造函数加属性
原型加方法
使用hasOwnProperty()方法可以检测一个属性是存在于实例还是原型中。
function Person(){ } Person.prototype.name = "flyer"; Person.prototype.age = 26; Person.prototype.job = "WED"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "bird"; alert(person1.name); // bird --来自实例 alert(person1.hasOwnProperty("name")); // true alert("name" in person1); // true alert(person2.name); // flyer --来自原型 alert(person2.hasOwnProperty("name")); // false alert("name" in person2); // true delete person1.name; alert(person1.name); // flyer --来自原型 alert(person1.hasOwnProperty("name")); // false alert("name" in person1); // true
2.原型与in操作符
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性。在默认的情况下,所有prototype属性都会自动获得一个constructor属性,这个属性包含一个指向prototype属性函数的指针。
关于原型模式的理解可以参考tom大叔的文章http://www.cnblogs.com/TomXu/archive/2012/04/16/2436460.html
3.更简单的原型语法
function Person(){ } Person.prototype = { name : "flyer", age : 26, job: "WED", sayName : function () { alert(this.name); } }; var person = new Person(); alert(person instanceof Object); //true alert(person instanceof Person); //true alert(person.constructor == Person); //false alert(person.constructor == Object); //true
4.原型的动态性
把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。
function Person(){ } var person = new Person(); Person.prototype = { constructor : Person, name : "flyer", age : 26, job: "WED", sayName : function () { alert(this.name); } }; person.sayName(); //error
5.原生对象的原型
可以像修改自定义对象的原型一样修改原生的原型。
String.prototype.startWith = function(){ //给String添加一个名为startsWith()的方法 return this.indexOf(text) == 0; } var msg = ‘hello world’; alert(msg.startWith(‘hello’)); // true
6.原型对象的问题
原型中所有属性是被很多实例共享的,对于包含引用类型的值来说,问题比较突出。
function Person(){ } Person.prototype = { constructor : Person, name : "flyer", age : 26, job: "WED", friends:["bird","stone"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("andy"); alert(person1.friends); alert(person2.friends); alert(person1.friends == person2.friends);
6.1.4 组合使用构造函数模式和原型模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["bing","bird"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("flyer",26, "WED"); var person2 = new Person("tolly",27, "UE"); person1.friends.push("cally"); alert(person1.friends); // bing bird cally alert(person2.friends); // bing bird alert(person1.friends === person2.friends); // bing bird alert(person1.sayName === person2.sayName); // bing bird
6.1.5 动态原型模式
把所有信息都封装在构造函数中,通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name,age,job){ //属性 this.name = name; this.age = age; this.job = job; //方法 if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } } var person = new Person("flyer", 26, "WED"); person.sayName();
6.1.6 寄生构造函数模式
在前几种都不适用,可以使用此模式,基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
6.1.7 稳妥构造函数模式
稳妥对象,指没有公共属性,其方法也不引用this对象。
不用this,不用new,适合安全环境。
6.2继承
许多OO语言都支持:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScript中无法实现接口继承。只支持实现继承。主要靠原型链来实现继承。
抽象:“抽”--把最主要的特征,跟问题相关的特征抽出来。
封装:看不到里面的东西,用好表面的功能就行了。
继承:从父类上继承出一些方法,属性。6.2.1 原型链(很少单独使用)
是实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实现,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系成立。如此层层递进,就构成了实例与原型的链条。
function ParentType(){ this.property = true; } ParentType.prototype.getParentValue = function(){ return this.property; }; // 继承了SuperType function SubType(){ this.subproperty = false; } SubType.prototype = new ParentType(); SubType.prototype.getSubValue = function(){ return this.subproperty; }; var instance = new SubType(); alert(instance.getParentValue()); // true /* * 确定原型与实例的2种方法: * 1.使用instanceof操作符 * 2.使用isPrototypeOf()方法 */ alert(instance instanceof Object); // true alert(instance instanceof ParentType); // true alert(ParentType.prototype.isPrototypeOf(instance)); // true
6.2.2 借用构造函数(很少单独使用)
在子类型的构造函数的内部调用超类型构造函数。用call()或者apply()方法。
function ParentType(){ this.colors = ["red","green","yellow"]; } function SubType(){ // 继承了SuperType ParentType.call(this); } var instance1 = new ParentType(); instance1.colors.push("blue"); alert(instance1.colors); var instance2 = new ParentType(); alert(instance2.colors);
6.2.3 组合继承(常用的继承模式)
也叫伪经典继承,指的是将原型链和借用构造函数的技术组合在一块,发挥两者之长的一种继承模式。其思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
function ParentType(name){ this.name = name; this.colors = ["red","green","yellow"]; } ParentType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ ParentType.call(this,name); this.age = age; } SubType.prototype = new ParentType(); SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("flyer",26); instance1.colors.push("blue"); alert(instance1.colors); instance1.sayAge(); instance1.sayName(); var instance2 = new SubType("bing",27); alert(instance2.colors); instance2.sayAge(); instance2.sayName();
6.2.4 原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function object(o){ function F(){} F.prototype = o; return new F(); } var person = { name: "flyer", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
6.2.5 寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的做了所有工作一样返回对象。
function object(o){ function F(){} F.prototype = o; return new F(); } function createAnother(original){ var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ // 以某种当时来增强这个对象 alert("hi"); }; return clone; //返回这个对象 } var person = { name:"flyer", friends:["bird","stone","bing"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi();
6.2.6 寄生组合式继承(是引用类型最理性的继承范式)
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
function object(o){ function F(){} F.prototype = o; return new F(); } function inheritPrototype(subType, superType){ //接收两个参数:子类型构造函数和超类型构造函数 var prototype = object(superType.prototype); //创建对象 创建超类型原型的一个副本 prototype.constructor = subType; //增强对象 为创建的副本添加constructor属性 subType.prototype = prototype; //指定对象 将新创建的对象赋给子类型的原型 } 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 instance1 = new SubType("flyer", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"flyer"; instance1.sayAge(); //29 var instance2 = new SubType("bing", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"bing"; instance2.sayAge(); //27