前言:
目前各位老铁们对“面向对象程序设计”都比较关注,咱们都需要剖析一些“面向对象程序设计”的相关资讯。那么小编也在网上网罗了一些有关“面向对象程序设计””的相关内容,希望小伙伴们能喜欢,你们一起来了解一下吧!下述内容主要讲述了《JavaScript高级程序设计(第3版)》第6章关于“面向对象的程序设计”。
ECMA-262把对象定义为:”无序属性的集合,其属性可以包含基本值、对象或者函数。”
一、理解对象
1. 属性类型
ECMAScript中有两种属性:数据属性和访问器属性。
数据属性包含一个值;访问器属性不包含值而定义了一个当属性被读取时调用的函数(getter)和一个当属性被写入时调用的函数(setter)。
(1)数据属性
(2)访问器属性
特性说明描述[[Configurable]]可配置能否删除、修改属性的特性[[Enumerable]]可枚举能否通过for-in循环返回属性[[Get]]属性被读取时调用的函数默认值undefined[[Set]]属性被写入时调用的函数默认值undefined修改属性默认的特性:Object.defineProperty(属性所在的对象, 属性, 描述符对象)
示例:数据属性
var person = {};Object.defineProperty(person, "name", { configurable: true, enumerable: false, writable: false, value: "lg"});
console.log(person.name); // "lg"for(var prop in person){ console.log(prop); // 未执行}person.name = "li";console.log(person.name); // "lg"
示例:访问器属性
var person = { _name: "ligang"};Object.defineProperty(person, "name", { configurable: false, enumerable: true, set: function(name){ /* 此处可以做其他操作 */ this._name = name; }, get: function(){ /* 此处可以做其他操作 */ return this._name; }});console.log(person.name); // "lg"for(var prop in person){ console.log(prop); // "_name"}person.name = "li";console.log(person.name); // "li"
可以通过Object.getOwnPropertyDescriptor(属性所在的对象, 属性)取得给定属性的描述符;通过Object.defineProperties(属性所在的对象, {属性1:描述符对象1, 属性2:描述符对象2})一次性定义多个属性。
二、创建对象
1. 工厂模式
工厂模式抽象了创建具体对象的过程。
function createPerson(name, age){ var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function(){ console.log(this.name); }; return obj;}var p1 = createPerson("z3", 26);var p2 = createPerson("l4", 27);
工厂模式可以解决创建多个相似对象的问题,但是会出现识别问题(即怎么知道一个对象的类型)。
2. 构造函数模式
function Person(name, age){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name); };}var p1 = new Person("z3", 26);var p2 = new Person("l4", 27);console.log(p1 instanceof Person); // true
可以标识类型(p1.constructor ==> Person),其方法都要在每个实例上重新创建一遍。
3. 原型模式
每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型创建的所有实例共享的属性和方法。
function Person(){}Person.prototype.name = "lg";Person.prototype.age = 26;Person.prototype.sayName = function(){ console.log(this.name);};var p1 = new Person();p1.name = "z3";console.log(p1.name); // "z3" 实例console.log("name" in p1); // trueconsole.log(p1.hasOwnProperty("name")); // truedelete p1.name;console.log(p1.name); // "lg" 原型console.log("name" in p1); // trueconsole.log(p1.hasOwnProperty("name")); // false
当为对象添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。hasOwnProperty()方法可以检测一个属性是否存在于实例中,还是存在于原型中;in操作符无论该属性存在于实例中还是原型中。
示例:判断属性存在于原型中还是对象中
/* 方式一:函数封装 */function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object);}hasPrototypeProperty(p1, "name"); // true 原型/* 方式二:原型扩展 */Object.prototype.hasPrototypeProperty = function(prop){ return !this.hasOwnProperty(prop) && (prop in this);};p1.hasPrototypeProperty("name"); // true 原型更简单的原型语法:
function Person(){}Person.prototype = { name: "lg", age: 26, friends: ["camile"], sayName: function(){ console.log(this.name); }};// 修正构造函数指向,不可枚举Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person});var p1 = new Person();var p2 = new Person();
p1.friends.push("Gavin");console.log(p1.friends); // ["camile", "Gavin"]console.log(p2.friends); // ["camile", "Gavin"]
如果我们的初衷就是所有实例共享一个数组,那么其符合预期;若想每个实例都有属于自己的全部属性,会存在上述问题。
4. 组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。每个实例都会有自己的一份实例属性的副本,但同时又共享着方法的引用,最大限度地节省内存。
function Person(name, age, friends){ this.name = name; this.age = age; this.friends = friends || [];}Person.prototype.sayName = function(){ console.log(this.name);};var p1 = new Person("Gavin", 26);var p2 = new Person("Camile", 26);p1.friends.push(["James"]);console.log(p1.friends); // ["James"]p2.friends.push(["Tom"]);console.log(p2.friends); // ["Tom"]
是目前ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法!
5. 动态原型模式
将构造函数和原型结合,不再独立。
function Person(name, age){ this.name = name; this.age = age; // 不存在情况下,才会添加 if(typeof this.sayName !== "function"){ Person.prototype.sayName = function(){ console.log(this.name); } }}var p1 = new Person("Gavin", 26);p1.sayName(); // "Gavin"// Person.prototype.sayName不会被执行var p2 = new Person("Camile", 26);
注意:不能使用对象字面量重写原型,其会切断现有实例与新原型之间的联系。
6. 寄生构造函数模式
其和典型的构造函数有略微的区别。
function Person(name, age){ var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function(){ console.log(this.name); }; return obj;}var p1 = new Person("ligang", 26);console.log(p1 instanceof Person); // falseconsole.log(p1.constructor); // Object
如果我们想创建一个具有额外方法的特殊属性,使用上述模式会达到很好的效果!!
function SpecialArray(){ var ary = new Array(); ary.push.apply(ary, arguments); ary.toPipedString = function(){ return this.join("|"); }; return ary;}var colors = new SpecialArray("red", "yellow", "blue");colors.toPipedString();
三、继承
JavaScript主要通过原型链实现继承。
1. 原型链
每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto__)。
function Super(){ this.property = true;}Super.prototype.getSuperValue = function(){ return this.property;};
function Sub(){ this.subProperty = false;}Sub.prototype = new Super(); // 将一个类型的实例赋给另一个构造函数的原型Sub.prototype.getSubValue = function(){ return this.subProperty;};
var instance = new Sub();console.log(instance.getSubValue()); // falseconsole.log(instance.getSuperValue()); // trueconsole.log(instance.constructor); // Superconsole.log(instance instanceof Sub); // trueconsole.log(instance instanceof Super); // true
问题:
(1)包含引用类型值的原型,会被所有实例共享;
(2)创建子类型的实例时,不能向父类型的构造函数中传递参数。
2. 借用构造函数
在子类构造函数的内部调用父类的构造函数。
function Super(name){ this.name = name; this.colors = ["red"];}
function Sub(name, age){ // 继承Super,可传递参数 Super.call(this, name); this.age = age;}
var instance = new Sub("Gavin", 26);instance.colors.push("blue");console.log(instance.name, instance.age); // Gavin 26console.log(instance.colors); // ["red", "blue"] 独立的colors副本console.log(new Sub().colors); // ["red"] 独立的colors副本
问题:方法都在构造函数中定义,函数复用无从谈起。
3. 组合继承
将原型链和借用构造函数组合一起。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
function Super(name){ this.name = name; this.color = ["red"];}Super.prototype.sayName = function(){ console.log(this.name);};function Sub(name, age){ Super.call(this, name); this.age = age;}Sub.prototype = new Super();Sub.prototype.sayAge = function(){ console.log(this.age);};
var instance1 = new Sub("Gavin", 26);instance1.color.push("blue");console.log(instance1.color); // ["red", "blue"]instance1.sayName(); // "Gavin"instance1.sayAge(); // 26
var instance2 = new Sub("Camile", 26);instance2.color.push("yellow");console.log(instance2.color); // ["red", "yellow"]instance2.sayName(); // "Camile"instance2.sayAge(); // 26
JavaScript中最常用的继承模式!!!
4. 原型式继承
function createObj(o){ function F(){} F.prototype = o; // 对o的一种浅复制 return new F();}
该方法等价于ECMAScript5中Object.create()方法只传入第一个参数。引用类型值的属性会共享相应的值。
5. 寄生式继承
在原型式继承基础上,继续改造。
function createAnother(original){ var clone = createObj(original); clone.sayHi = function(){ console.log("Hi"); }; return clone;}var person = { name: "LIGANG", age: 26};var anotherPerson = createAnother(person);anotherPerson.sayHi();
对象方法不能被复用!
【更多内容,可关注微信公众号「Super 前端」】
标签: #面向对象程序设计