JavaScript 面向对象程序设计

理解对象

ECMAScript 5中只有内部才有的特效:数据属性和访问器属性
数据属性
- Configurable 默认true,表示是否通过delete 删除重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性
- Enumerable 默认true,表示能否通过fore-in 循环返回属性
- Writable 默认true,能否修改属性的值
- Value 默认undefined,包含这个属性的数据值
- 修改属性默认的特性Object.defineProperty(obj,proName,descriptorObj) 一旦configurable改为false再也不能变回原来的配置
访问器属性
- Configurable 默认true,表示是否通过delete 删除重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性
- Enumerable 默认true,表示能否通过fore-in 循环返回属性
- Get 读取属性时调用的函数,默认值为undefined
- Set 写入属性时调用的函数,默认值为undefined
- 同样通过 Object.defineProperty()来修改属性默认值_propertyName 在属性名前用下划线表示只能通过对象方法访问的属性 支持ECMAScript 5 get/set方法的浏览器为IE9+(部分IE8) FireFox4+,Safiri 5+ Opera12+ 和Chrome,在这个方法之前是两个非标准的方法_defineGetter_()/_defineSetter()_
- 定义多个属性Object.defineProperties(obj,{})
- 获取属性的特性Object.getOwnPropertyDescriptor(obj,'propertyName')

创建对象

  • 工厂模式,就是在函数中声明一个对象并设置对象属性值后返回该对象
  • 构造函数 ,没显示的创建对象,直接将属性方法赋给this对象,没有return语句。构造函数始终以一个大写字母开通,创建新的实例必须用new 操作符,构造函数可以将对象标识为一种特定的类型,这才是胜过工程模式的地方。
    通过new调用的函数均为构造函数,不通过new来操作的函数和其他的函数没啥区别。当作构造函数使用时,通过生成对象来访问,普通的函数直接用window来访问。
    Person.call(o,'aaa','bbb'); 将参数传给Person构造函数后返回给o对象,等同于 o = new Person('aaa','bbb');
    构造函数的问题 每个方法都要在每个实例上创建一遍,构造函数中的函数也是对象等价于new Function();,每个构造函数实例对象都包含着一个不同的Function实例,不同实例的同名函数是不相等的
    给构造函数内的属性设置一个函数名,函数在外部实现时,虽然各个实例共享了这个函数,但在全局定义的函数只能让某个对象调用全局作用域有点名不副实,让人无法接受的是对象要定义多个方法时就会有好多个全局变量。刚好这个问题可以交给原型解决

原型

每个函数都有一个prototype(原型)属性,该属性是个指针,指向一个对象,这个对象包含由特定类型的所有实例共享的属性和方法 不需要再构造函数中添加共享的实例属性或函数,将他们全部添加到函数的原型当中即可。
只要新创建函数就会为该函数创建一个prototype属性,该属性是个指针,指向函数的原型对象。所有的原型对象都会自动获取一个constructor属性(当然constructor也是共享的),该属性指向property属性所在的函数的指针。
创建自定义的构造函数,其原型对象默认会取得constructor属性,至于其他的则都是从Object对象继承而来的,实例当中也包含一个对象[[prototype]],虽然在脚本中没有标准的访问方式,但FireFox,Safari,Chrome都支持一个属性__proto__,指向函数的原型对象

function Person(){};
Person.prototype.name = "LILI";
var a = new Person(); 
alert(a.__proto__ == Person.prototype); //true
alert(a.constructor); //Person
alert(a.name); //LILI (a.__proto__.name)
alert(Person.prototype.isPrototypeOf(a)) //true
alert(Object.getPrototypeOf(a)) // Person.prototype IE9+ FF3.5+ Safari 5+ chrome
alert(Person.prototype.constructor);// function Person(){}

虽然可以通过对象的实例访问保存在原型中的值,但不能通过对象实例重写原型中的值,若果给实例中添加的属性和实例原型中一个属性同名,那么在实例中创建的属性会屏蔽原型中的那个属性。只是阻止我们去访问原型中的那个值,但不会修改那个属性,即使将这个属性设置为null 可以通过delete操作符来删除对象的实例属性来重新访问原型中的对象
hasOwnProperty()检测一个属性是否存在于实例当中
单独使用in 操作符时,在对象能够访问到给定属性时返回true,不管是实例当中还是原型当中
hasPrototypeProperty(obj,property) 只要属性存在于实例当中,就返回flse; 只是存在于原型当中返回true
Object.keys() 获取对象(自己定义)所有可枚举的实例属性
Object.getOwnPropertyNames() 获取所有的实例,无论它是否可枚举

更简单的原型写法,相当于创建了一个新原型对象,地址肯定发生变化,导致对象实例访问出错

Person.prototype = {
    //constructorPerson,  会破坏可枚举的属性,原生的可枚举是false,可以通过Object.defineProperty()来重设,但要兼容ES5的浏览器
    name: "a"
};

此时的Person.prototype.constructor指向的是Object,不再是Person,本质上重写了prototype对象

原型的动态性
可以随时给原型添加属性和方法,并且修改能立即生效,当通过重写时(即上面简单的原型写法) 会导致实例无法访问原型中的属性,因为产生了一个新的对象

原生对象的原型
原生的应用类型都是在其构造函数的原型上定义了方法,不仅可以取得默认的方法引用,还能定义新的方法。产品化的程序中不推荐修改原生对象的原型。

原型对象的问题

  • 忽略了为构造函数传递参数的初始化这一过程
  • 最大的问题还是共享的本性导致的 对于引用类型的值来说,问题比较突出(重写原型时) 假如有一个原型对象的属性为一个数组,当实例对象给它添加内容时导致其他的实例也能访问的添加的内容

组合构造函数和原型模式

ECMAScript中使用最广泛,认同度最高的一定义引用类型的模式
构造函数模式定义实例属性,原型模式定义方法和共享属性

动态原型模式

把所有的信息都封装在构造函数中,通过构造函数的初始化原型,然后根据自己的需要来添加原型属性

function Person(name){
    this.name = name;
    if(typeOf this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        };
    }
}
var mf = new Person("a");
mf.sayName();

寄生构造函数模式

Fun.apply(obj,arguments) obj代替Fun中的this,argument传给Fun
假设需要一个具有额外方法的特殊数组

function SpecialArray(){
    var values = new Array();
    values.push.apply(values,arguments);
    values.toMethod = function(){};
    return values; // 关键
}

稳妥构造函数模式

没有公共属性,而且其他方法也不引用this的对象

function Person(name){
    var o = new Object();
    //创建私有变量和方法
    o.sayName = function(){};
    return o; 
}

继承

  • 原型链作为实现继承的主要方法,基本思路就是利用原型让一个应用类型继承另一个引用类型的属性和方法
  • 让另一个类型的实例指向原型对象(subType.prototype = new SuperType()),此时的subType原型对象将包含一个指向另一个原型的指针([[prototype]]/__proto__),相应的另一个原型中也包含着一个指向另一个构造函数(SuperType())的指针(constructor) 假如另一个原型又是另一个类型的实例,这样层层递进,就构成了原型链的基础概念
function SuperType(){this.text = true;} 
SuperType.prototype.getSuper = function(){return this.text;};
function SubType(){this.text = flase;} 
SubType.prototype = new SuperType(); 
SubType.prototype.getSub = function(){return this.text;}; 
var instance1 = new SuperType(); 
var instance2 = new SubType();
console.log(instance1.getSuper()); //true
console.log(SubType.prototype.constructor); // ƒ SuperType(){this.text = true;}
console.log(instance1.__proto__);//{getSuper: function(){},constructor:..}
console.log(instance2.__proto__);//SuperType{text:true;getSub: function(){...}}
console.log(SuperType.prototype.constructor); //Object.prototype

把 一个需要继承的构造函数实例指向SuperType.prototype时(本质就是重写原型对象),SubType.prototype.constructor 的指向也发生了改变,指向了需要继承的这个构造函数(也就是说它就是一个实例了)

SubType.prototype = new SuperType(); 
SubType.prototype.getSub = function(){return this.text;}; 

如果把上面两行的位置互换,则导致instance2.__proto__得到的SuperType中没有getSub()函数

确定原型和实例之间的关系

  • instanceOf 用实例和原型链中出现的构造函数来判断,出现就返回true
  • isPrototypeOf()方法,Object.prototype.isPrototypeOf(instance) ,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型

子类型有时候需要覆盖超类中的某个方法或给超类中添加不存在的方法,不管怎样给原型添加的方法的代码一定要放在替换原型的语句后面(SubType.prototype = new SuperType(); ),子类重写超类中的方法时会屏蔽原来的方法(子类的实例调用时)
还有一点需要提醒的就是通过原型链实现继承的时候,不能使用对象字面量创建原型方法,这样做会重写原型链,导致SubType.prototype = new SuperType();无效。在刚刚把SuperType()的实例赋值给原型然后又将原型替换成一个对象字面量

原型链的问题

最主要的问题就是包含引用类型的原型,引用类型的原型属性会被所有的实例共享,这也是为啥要在构造函数中构建而不是在原型对象中定义属性的原因。通过原型来实现继承的时候,原型会变成另一个类型的实例(引用),于是原先的实例属性也就顺理成章的变成现在的原型属性了

function SuperType(){
    this.colors = ['red','blue'];
}
function SubType(){
}

SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");

var instance2 = new SubType();
alert(instance2.colors); //red,blue,black

第二个问题就是创建子类型的时候,不能向超类的构造函数中传递参数,基本上实践中很少单独使用原型链

解决原型中引用类型带来的问题

借用构造函数

 //改写前面的SubType()
 function SubType(){
    SuperType.call(this);//将Subtype的实例的环境调用到SuperType(),就会在新SubType对象执行SupeType()函数中定义的所有对象初始化代码
}
var instance1 = new SubType();
instance1.colors.push("black");

var instance2 = new SubType();
alert(instance2.colors); //red,blue

理解对象字面量用构造函数实现

//对象字面量
var o1 = {
    p:”I’m in Object literal”,
    alertP:function(){
        alert(this.p);
    }
}

//构造函数
function CO(){
    this.p = “I’m in constructed object”;
    this.alertP = function(){
        alert(this.p);
    }
}
var o2 = newCO();

等同于执行了
var obj  ={};
obj.__proto__ = CO.prototype;
CO.call(obj); //重点,只要创建实例就会执行SuperType()中所有对象的初始化代码
return obj;

第三行 将构造函数的作用域赋给新对象,因此CA函数中的this指向新对象obj,然后再调用CO函数。于是我们就给obj对象赋值了一个成员变量p,这个成员变量的值是” I’min constructed object”。

ConstructorF.call(obj,"param") 就是将Constructor中的环境作用域指向obj对象后再执行Constructor构造函数,传递的参数是param

传递参数

function SuperType(name){
    this.name = name;
}

function SubType(){
    SuperType.call(this,"HaHa");//先调用超类,然后给子类型写属性,这样构造函数就不会重写子类型属性
    this.age = 29; //实例属性
}
var instance = new SubType();
alert(instance.name); //HaHa
alert(instance.age); //29

缺陷 方法都在构造函数中,函数复用无法谈起

组合继承 常用继承模式##

将原型链和借用构造函数技术组合到一起,发挥二者之长,背后的思路是使用原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承,在原型定义的方法实现了函数的复用,又能保证每个实例都有它自己的属性。

function SuperType(name){
    this.name = name;
    this.colors = ["red","blue"];
}
Super.prototype.sayName = function(){
    alert(this.name);
}

function SubType(name,age){
    SuperType.call(this,name);//第二次调用SuperType()
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); //第一次调用SuperType()
SubType.prototype.constructor = SubType;//不重写,则原先指向Super构造函数。需要给子类自己定义自己的共享方法
SubType.prototype.sayAge = function(){
    alert(this.age);
}

var instance1 = new SubType("HaHa",29);
instance1.colors.push("black");
alert(instance1.colors); // red,blue,black
instance1.sayName();//HaHa
instance1.sayAge();// 29

var instance2 = new SubType("Tom",18);
alert(instance2.colors);//red,blue
instance2.sayName(); //Tom
instance2.sayAge();//18

原型式继承

没有严格意义上的构造函数,基于已有对象创建新对象,同时还不必因此创建自定义类型,给出函数

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

ECMAScript 5 新增了Object.create()方法规范原型式继承,接收两个参数,一个用作新对象原型的对象和(可选)新对象定义额外属性的对象,在一个参数的情况下Object.create() 和object()方法的行为相同

寄生式继承

和原型式继承精密结合,代码示范(缺点:不能做到函数复用而降低效率)

function createAnother(original){
    var clone = object(original);
        clone.sayHi = function(){
            alert("Hi");
        }
    return clone;
}

寄生组合式继承

组合继承会调用两次超类构造函数,参见组合上面的组合调用

SubType.prototype = new SuperType(); 
SubType.prototype.constructor = SubType;

可以用寄生式来代替,减少一次调用超类,避免了在SubType.prototype中创建不必要和多余的属性

function inheritPrototype(subType,superType){
    var prototype = object(subType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

猜你喜欢

转载自blog.csdn.net/u013270347/article/details/80855530