JS学习笔记整理五 面向对象的程序设计

面向对象的程序设计

理解对象

属性类型

对象的属性类型包含:数据属性、访问器属性javascript

  1. 数据属性vue

    • 可配置:Configurable,是否能用delete删除。可否修改属性特性。此值一旦设为false,就不可逆。
    • 可修改:Writable,可否修改value值。
    • 可枚举:Enumerable,可否经过for...in枚举。
    • 值:Value
  2. 访问器属性java

    没有value和writable,多了一对get和set。数组

    无论writable真假,只要configurable为真,经过Object.defineProperty就能把特性改成访问器属性。浏览器

Object.getOwnPropertyDescriptor(o,”prop”);:读取属性特性。安全

Object.getOwnPropertyDescriptors(o);:读取全部属性特性。闭包

犀牛书里把访问器accessor称为存取器,我以为这样更形象。可使用直接量语法的扩展语法来定义属性,好比下面两种方法:app

var o={
    name:'tiedan',
    get ga(){return this.name},
    set ga(value){this.name=value}
}
var p={name:'tiedan'};
Object.defineProperty(p,'ga',{
    set:function(value){this.name},
    get:function (){return this.name}});
console.log(Object.getOwnPropertyDescriptors(o));
console.log(Object.getOwnPropertyDescriptors(p));
/*{ name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get ga],set: [Function: set ga],enumerable: true,configurable: true } } { name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get],set: [Function: set],enumerable: false,configurable: false } }*/
//方括号里有细微区别。 
复制代码

使用Object.defineProperty或者Ojbect.defineProperties,不显示定义configurable、writable、enumerable,则这三个特性默认值都为false函数

vue的核心实现就是利用Object.definProperty劫持属性的get和set特性,来实现双向绑定的。测试

Object.preventExtensions对象不能添加新属性(可删除原有属性)。可经过Object.isExtensible来检测对象是否能扩展。

Ojbect.seal能够密封对象,密封后的对象不能删除和添加属性。全部属性特性的[[configurable]]都为false。可经过Object.isSeal来检测对象是否为密封对象。

Object.freeze能够冻结对象,比Object.seal更进一步,对象不能有任何变化。全部特性均为false。可经过Object.isFrozen来检测对象是否为冻结对象。

经过Object.getOwnPropertyNames看到的Object.prototype的属性,这部分属性都是可继承的。

[ 'constructor',             //给全部对象的构造器属性,建立函数时,就会自动建立函数原型,和其constructor属性,若是重写原型,则继承这个
 '__defineGetter__',      //一些浏览器的非标准方法,在Object.defineProperty不支持的时候,能够尝试用这个定义属性的特性。
 '__defineSetter__',      //同上相似
 'hasOwnProperty',     //检测对象是否有自有的属性
 '__lookupGetter__',    //非标准方法,用来返回命名属性的Getter方法
 '__lookupSetter__',    //同上相似
 'isPrototypeOf',         //是否为检测对象的原型。其原理同instanceof同样都是查找原型链,只是instanceof后面是构造函数
 'propertyIsEnumerable',//属性是不是可枚举的,这个和Object.getOwnPropertyDescriptor()里获得的enumberable属性是同样的
 'toString',                  //
 'valueOf',                  //
 '__proto__',               //通常是内部属性,一个原型指针
 'toLocaleString' ]     //
复制代码

也能够经过Object.getOwnPropertyNames看看Object的属性和方法。

建立对象

犀牛书例子,最早由道格拉斯.克罗克福德提出,这我的在两本书里出现N屡次。下面的是犀牛书p122的例子,红皮书相似的例子在p169。 Object.create()建立一个新对象,参数是新对象的原型。create方法有两个参数,第二个参数与defineProperties的第二个参数同样。

function inherit(p){
    if(p==null) throw TypeError();
    //首先排除null,自己typeof就可能有null。Object.create是能够传null的
    if(Object.create){
        return Object.create(p);
    }
    var t=typeof p;
    if(t!="object"&&t!="function")throw TypeError;//不能是基本类型值
    function a(){}
    a.prototype=p;
    return new a();
  }
复制代码

new的实现大体以下:

o = {};
o.__proto__=f.prototype;
f.apply(o,arguments);
复制代码

因此new Object();不如直接用{}执行快就是由于这个缘由吗?

自定义的new以下:

function New(f) {
  //返回一个func
  return function () {
    o = {};
    o.__proto__=f.prototype;
    f.apply(o, arguments);//继承父类的属性
    return o; //返回一个Object
  }
}
复制代码

经过new fn();或者Object.create(p);其实都是返回新对象的方法。

工厂模式

“生产对象”,因此通常直接调用。不使用new(使用也能返回正常对象)。

function Person(name,age){
    var o=new Object();
    o.name=name;
    o.age=age;
    return o;
}
复制代码

弊端:解决了对象建立问题,但没有解决对象识别问题

构造函数模式

function Person(name,age){
    this.name=name;
    this.age=age;
}
复制代码

没有显示建立对象,直接将属性和方法赋给this,(能够)没有return。经过new来调用。

实际上步骤:

var o={};//建立新对象
o.__proto__=fn.prototype;//对象的原型指针赋值
fn.apply(o,arguments);//this指向新对象,执行构造函数代码
return o;//返回新对象
复制代码

new fn()就是相似上面的过程。构造函数fn的执行实际上就是给o对象赋值操做的过程。

弊端:方法重复定义,即使放到全局也只适合对象调用,还容易污染全局做用域。

原型模式

function Person(){
}
Person.prototype.name='tiedan';
Person.prototype.age='1';
复制代码

对于原型有两种写法:

fn.prototype.p1=value1;
fn.protptype.p2=value2;
复制代码
fn.prototype={
  p1:value1,
  p2:value2
}
复制代码

第二种看起来更清楚,可是存在问题,由于这至关于重写原型对象。以前定义好的一些属性方法可能会丢失,constructor属性须要显示从新指向构造函数。要不就没了。在第二种重写原型以前不能先new fn,这会致使重写原型以后切断了构造函数fn和最初的原型之间的联系。由于prototype属性自己存储的就是一个指针。而对象和原型之间的联系仅靠__proto__存储的一个指针。至关于原型链出了问题,没法经过这个属性找到新的原型对象,得到新对象的属性和方法。说白了就是fn和obj指向了不一样的原型对象。

function Person(name,age){
    this.name=name;
    this.age=age;
}
Person.prototype.height=100;
var p1=new Person("tiedan",36);
Person.prototype={
    constructor:Person,
    height:180
}
var p2=new Person("someone",29);
console.log(p1 instanceof Person);//false,由于其__proto__指向的已经不是最新的Person.prototype。经管如此,p1.constructor.prototype仍是对的。
console.log(p1.height);//100
console.log(p2 instanceof Person);//false
console.log(p2.height);//180
复制代码

弊端:除了上面所说的,还有就是由于原型对象的属性和方法都是实例共享的。那么对于属性是引用类型,好比数组,你们也是共享的同一个引用对象。修改这类属性值,实际上会影响到全部实例。

建议属性值是引用类型的,不要用原型模式,除非你就须要这种共享引用类型的方式。

in运算符有两种用法,一种用于for...in。另外一种在单独使用时,in操做符会在经过对象可以访问给定属性时返回true,不管该属性存在于实例中仍是原型中!

function hasPrototypeProperty(object,name){
  return !object.hasOwnProperty(name)&&(name in object);
}
复制代码

动态原型模式

function Person(name,age){
    this.name=name;
    this.age=age;
    if(typeof this.sayName!='function'){
        Person.prototype.sayName=function(){console.log(this.name);}
    }
}
复制代码

相对比较完美的一种模式。

使用这种模式,一样不能用对象字面量重写原型。思考一下new的实现步骤,就能明白,构造函数里重写原型,则必然会在o对象建立以后切断与原有原型的联系。

寄生构造函数模式

function Person(name,age){
    var o=new Object();
    o.name=name;
    o.age=age;
    return o;
}
var tiedan=new Person('铁蛋',1);
console.log(tiedan instanceof Person);//false
复制代码

这个模式和工厂模式的函数彻底同样,只是这个直接经过new来调用。测试了一下,当作构造函数使用,末尾return会代替构造函数正常的return值。

弊端:返回的对象和构造函数没什么关系。不能依赖instanceof来检测对象。(返回的其实是里面的o类型)

稳妥构造函数模式

durable object持久对象 克罗克福德发明

function Person(name,age){
    var o=new Object();
    o.sayName=function(){
        alert(name);
    };
    return o;
}
var tiedan=Person('铁蛋');
复制代码

相似于寄生构造函数模式。没有公共属性,不引用this对象。适合在安全环境或防止数据被其余应用程序改动时使用。1.不引this,2.不用new

实际上是利用了闭包原理。即使对象被添加属性和方法,也没法篡改原始值。

继承

继承分为接口继承和实现继承

红皮书p162说es中没法实现接口继承,其实es4版本是实现了接口继承的(as3.0)。说不定哪天interface就从保留字变关键字了。

原型链

  1. 别忘记默认原型:Object.prototype

  2. 肯定原型和实例的关系,instanceofisPropertyOf

  3. 谨慎的定义方法,方法覆盖,红皮书p166必须用SuperType实例替换SubType的原型后再定义原型方法,不然原型对象一重写就没了。

  4. 原型链的问题:

    相似以前原型模式引用类型的问题。SubType.prototype=new SuperType();实际上就是重写原型。原型属性包含引用类型就容易出现意料以外的状况。

借用构造函数

constructor stealing 明明是偷非说是借~

function SubType(age){
 SuperType.call(this,"tiedan");
 this.age=age;
}
复制代码
  1. 可向父类构造函数传参
  2. 问题:父类原型对子类不可见。

组合继承

将借用构造函数和原型链技术结合

  1. 借用构造函数获得实例属性

  2. 原型链技术继承原型属性和方法,同时还能够扩充本身的原型方法

SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.prop=…
复制代码

原型式继承

又是道格拉斯.克罗克福德... 提到了object(o)函数,犀牛书里名为inherit(p)

var person={};
var anotherPerson=Object.create(person);
anotherPerson.prop=...
//继承的引用类型会被实例共享
复制代码

Object.create在使用一个参数的时候,行为和object(o);相同,使用两个参数的时候,第二个参数和Object.defineProperties的第二个参数格式相同。以这种方式指定的属性会覆盖原型对象的同名属性。

其实inherit也能够加第二个参数,也就是作一个浅复制,这能够参看犀牛书的浅复制。extend方法。

var p2=Object.create({},{"hehe":{value:30 }});
console.log(p2);//{}
//什么都看不见,由于和Object.defineProperties同样,enumerable不显示定义默认为false,因此看不到。
复制代码

寄生式继承

克罗克福德推广的

function createAnother(o){
    var anotherPerson=Object.create(o);
    anotherPerson.sayHello=function(){console.log('hello');}
    return anotherPerson;
}
var anotherPerson=createAnother({});
anotherPerson.sayHello();
复制代码

主要考虑对象而不是自定义类型和构造函数的状况下使用

寄生式继承的思路与寄生构造函数和工厂模式相似。建立一个用来封装继承过程的函数。在函数内部以某种方式加强对象,最后像本身作了全部工做同样返回对象。

寄生组合式继承

以前的组合式继承最大的问题是要调用两次父类构造函数,一次是借用构造函数,一次是重写子类原型。其实不必调用两次构造函数。能够只调用一次借用构造函数,另外一次寄生式new一个空构造函数。只为了复制父类原型属性和方法。

function SuperType(name){
    this.name=name;
    this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=function(){console.log(this.name);}
function subType(name,age){
    SuperType.call(this,name);//第二次调用
    this.age=age;
}
SubType.prototype=new SuperType();//第一次调用
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){console.log(this.age);}

复制代码

改成:

function inheritPrototype(SubType,SuperType){
    var prototype=Object.create(SuperType.prototype);//建立对象
    prototype.constructor=SubType;//加强对象
    SubType.prototype=prototype;//指定对象,依然重写原型啊...
}

function SuperType(name){
    this.name=name;
    this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=function(){console.log(this.name);}

function SubType(name,age){
    SuperType.call(this,name);//第二次调用
    this.age=age;
}
inheritPrototype(SubType,SuperType);//第一次调用
SubType.prototype.sayAge=function(){console.log(this.age);}

复制代码

上面例子依然重写了原型啊。因此,顺序很重要。不然,最后两行换一下位置,sayAge就被盖掉了。

相关文章
相关标签/搜索