JS5-面向对象的程序设计

理解对象

ECMA-262中把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数”。
对象相当于一组没有特定顺序的值。

对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。

想象成散列表:无非是一组名值对,其中值可以是数据或函数。

属性类型

数据属性

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。

  1. [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
  2. [[Enumerable]]:表示能否通过for-in循环返回属性。
  3. [[Writable]]:表示能否修改属性的值。
  4. [[Value]]:包含这个属性的数据值。默认undefined

使用ECMAScript 5中的Object.defineProperty()方法修改属性的默认特征。
参数: 属性所在对象、属性的名称、一个描述符对象(configurable、enumerable、writable、value)。
一旦把属性定义为不可配置的,就不能再把它变回可配置了。

访问器属性

访问器不包含数据值,是一对getter(负责返回有效值)和setter(负责决定如何处理数据)函数。

  1. [[Comfigurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
  2. [[Enumerable]]:表示能否通过for-in循环返回属性
  3. [[Get]]:在读取属性时调用的函数,默认undefined
  4. [[Set]]:在写入属性时调用的函数,默认undefined

必须通过Object.defineProperty()来定义。
设置一个属性的值会导致其他的属性发生变化。

/*访问器属性*/
var book = {
    _year:2004,
    edition : 1
}
Object.defineProperty(book,"year",{
    get:function(){
        return this._year;
    },
    set:function(newValue){
        if(newValue > 2004){
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    }
});
var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
console.log(descriptor.value);//undefined
console.log(descriptor.enumerable);//true
console.log(typeof descriptor.get); //undefined

var descriptor = Object.getOwnPropertyDescriptor(book,"year");
console.log(descriptor.value);//undefined
console.log(descriptor.enumerable);//false
console.log(typeof descriptor.get); //function

定义多个属性

Object.defineProperties()

可以通过描述符一次定义多个属性。

参数:要添加或修改其属性的对象,与第一个对象中要添加或修改的属性一一对应

var book = {};
Object.defineProperties(book,{
    _year:{
        writable:true;
        value:2004
    },
    edition{
        writable:true;
        value:1
    },
    year:{
        get:function(){
            return this._year;
        },
        set:function(newValue){
            if(newValue>2004){
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
console.log(descriptor.value);    //2004
console.log(descriptor.configurable); //false
console.log(typeof descriptor.get); //undefined

var descriptor = Object.getOwnPropertyDescriptor(book,"year");
console.log(descriptor.value);//undefined
console.log(descriptor.enumerable);//false
console.log(typeof descriptor.get); //function

读取属性的特征

Object.getOwnPropertyDesciptor()
参数: 属性所在的对象,要读取属性描述符的属性名称

创建对象

Object构造函数

var person = new Object();
person.name = "Nicholas"
person.age = 29;
person.job = "software Enginner";

person.sayName = function(){
    alert(this.name);
}

对象字面值

var person = {
    name :"Nicholas",
    age:29,
    job:"software Engineer",

    sayNmae: function(){
        alert(this.name):
    }
}

工厂模式

用函数来封装以特定接口穿件对象的细节

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("Nicholas",29,"software Engineer");

优点:解决了创建多个相似对象的问题
缺点:没有解决对象识别的问题,即怎样知道一个对象的类型。

构造函数模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person1.sayName == person2.sayName);//false

构造函数要以大写字母开头,而非构造函数以一个小写字母开头。

所有的对象都集成自Object。

这种方法定义的构造函数是定义在Global对象中的。

优点:创建自定义的构造函数意味着将来可以将它的实例标示为一种特定的类型。

  1. 构造函数当作函数,构造函数和普通函数的区别是,调用它们的方式不同。
  2. 任何函数 只要通过new操作符来调用,就可以作为构造函数。

缺点:1.每个方法都要在每个实例上重新创建一遍。
2.不同是实例上的同名函数是不相等的。
3.创建两个完成同样任务的Function实例的确没有必要
4.通过把函数的定义转移到构造函数外部来解决这个问题:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);

}
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person1.sayName == person2.sayName);//true

5.把sayName()对象定义到全局作用域中,虽然可以解决上述问题,但是问题:
1)在全局作用域中定义的同一个对象实例上只能被某个对象调用,这样全局作用域有点名不副实;
2)如果对象需要定义很多方法,那么就要定义很多全局函数,自定义的引用类型没有封装的意义了。

原型模式

只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数原型对象。
在默认情况下,所有的对象原型会自动创建一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。
每个函数在调用时,都会取得两个特殊变量 :this 和arguments
每个函数都有一个prototype(原型属性),是一个指针,指向一个对象。

用途:包含可以由特定类型的所有实例共享的属性和方法。

prototype是通过调用构造函数而创建的那个对象实例的原型对象。

优点:可以让所有对象实例化它包含的属性和方法。

不必在构造函数中定义对象的实例信息,而是直接添加到原型对象中。

constructor构造函数

isPrototypeOf() 确定对象之间是否存在原型的关系

Object.getPrototypeOf() 返回实际对象的原型

hasOwnProperty() 检测一个属性是否属于实例中,还是存在与原型中。只在给定属性存在于对象实例中时,返回true

getOwnPropertyDescriptor() 只能用于实例属性,要取得原型对象的描述符,必须直接在原型对象上调用getOwnPropertyDescriptor()方法

原型与in操作符

in 操作符

  1. 单独使用:
    会在通过对象能够访问属性时返回true,无论属性存于实例中还是原型中。

    function hasPrototypeProperty(object,name){
        return !object.hasOwnProperty(name) && (name in object);
    }
    
  2. for-in循环中使用
    返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在与原型中属性。屏蔽了原型中的不可枚举属性的实例属性也会在for-in循环中返回。

Object.keys()

该方法接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

var keys = Object.keys(Person.prototype);
console.log(keys); //["name", "age", "job", "sayName"]
console.log(Object.keys(person1)); //返回的是实例属性如果没有定义,是空[]
person1.name = "hello";
person1.address = "xian";
console.log(Object.keys(person1)); //["name", "address"]

Object.getOwnPropertyNames()

得到所有的实例属性,无论它是否是可枚举的。

更简单的原型语法

function Person(){

 }
 person.prototype = {
     name : "Nicholas",
     age : 29,
     job : "Software Engineer",
     sayName : function(){
         console.log(this.name);
     }
 };

问题:这个时候,constructor属性不在指向Person了,
此时constructor无法确定对象的类型了

function Person(){

}

Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};
var friend = new Person();

console.log(friend instanceof Object); //true
console.log(friend instanceof Person); //true
console.log(friend.constructor == Person); //false
console.log(friend.constructor == Object);//true

可以将constructor指向Person,从而确保属性能访问到适当的值。
这种方式会导致constructor的属性会导致它的[[Enumerable]]被设置为true。

 function Person(){

}
Person.prototype = {
    constructor : Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};
var friend = new Person();
console.log(friend instanceof Object); //true
console.log(friend instanceof Person); //true
console.log(friend.constructor == Person); //true
console.log(friend.constructor == Object);//false

ECMAScript 5,Object.defineProperty()用来重设构造函数

function Person(){

}

Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};
Object.defineProperty(Person.prototype,"constructor",{
    enumerable : false,
    value:Person
});
var friend = new Person();

console.log(friend instanceof Object); //true
console.log(friend instanceof Person); //true
console.log(friend.constructor == Person); //false
console.log(friend.constructor == Object);//true

原型链式动态

  1. 实例和原型之间是松散连接关系
  2. 可以随时为原型添加属性和方法,并且修改能立即反应在所有对象实例中
  3. 但是如果重写整个原型的话,把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。

元素对象的原型

原生对象是在其构造函数的原型上定义了方法。

String.prototype.startWith = function(text){
    return this.indexOf(text) == 0;
};
var msg = "Hello world!";
console.log(msg.startWith("Hello"));//true

原型对象的问题

  1. 省略了为构造函数传递初始化参数这一环节,结果所有的实例在默认情况下都会取得相同的属性值。
  2. 最大问题:共享本质导致的。
  3. 共享对于函数适合,但是对于基本的属性值就不适合了。
  4. 实例一般都是要用于属于自己的全部属性的。

组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性

原型模式用于定义方法和共享的属性。

使用这种模式还可以像构造函数中传入参数。

可以定义引用类型的模式。

function Person(name,age,job){
    this.name = name,
    this.age = age,
    this.job = job,
    this.friends = ["shelby","court"]
}
Person.prototype = {
    constructor :Person,
    sayName: function(){
        console.log(this.name);
    }
}
var person1 = new Person("Nicholas",29,"Software Engineer");
var Person2 = new Person("Greg",27,"Doctor");
person1.friends.push("Van");
console.log(person1.friends); //["shelby", "court", "Van"]
console.log(person2.friends); //["shelby", "court"]
console.log(person1.friends == person2.friends); //false

动态原型模式

可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
使用动态原型,不能使用对象字面值重写原型。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["shelby","court"];
    if(typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}
var friend = new Person("Nicholas",29,"Software Engineer");
friend.sayName();

寄生构造函数模式

创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后在返回新创建的对象。

function Person(name,age,job)
{
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    }
    return o;
}
var friend = new Person("Nicholas",29,"Software Engineer");
friend.sayName();
  1. 除了new操作符 和 把使用的包装函数叫构造函数之后 和 工厂模式没有区别
  2. 适用情况:创建一个具有额外方法的特殊的对象。
  3. 不能依赖instanceof操作符来确定对象类型

稳妥构造函数模式

稳妥对象:没有公共属性,而且其方法也不引用this的对象
适合情况:在一些安全的环境中,或者在防止防护局被其它应用程序改动时使用。

与寄生模式不同处:

  1. 新创建的实例方法不引用this
  2. 不使用new操作符调用构造函数
  3. 不能依赖instanceof操作符来确定对象类型

    function Person(name,age,job){

    var o = new Object(); 
    /*可以在这里定义私有变量和函数*/
    o.sayName = function(){
        console.log(name);
    }
    return o;
    

    }
    var friend = Person(“Nicholas”,29,”Software Engineer”);
    friend.sayName();

继承

原型链

利用原型链让一个引用类型继承另一个引用类型的属性和方法。

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都会包含一个指向原型对象的内部指针。

    /*原型链 */
function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.Subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){
    return this.Subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()); //true

console.log(instance instanceof Object);//true
console.log(instance instanceof SuperType);//true
console.log(instance instanceof SubType);//true

console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(SubType.prototype.isPrototypeOf(instance));//true
console.log(SuperType.prototype.isPrototypeOf(instance));//true

确定原型和实例的关系

instanceof操作符

isPrototypeOf()方法

注意:

给原型添加方法的代码一定要放在替换原型的语句之后。

使用原型链实现继承时,不能使用对象字面值创建原型方法。

问题:

  1. 包含引用类型值的原型。引用类型共享了值。

  2. 在创建子类型的实例时,不能向超类型的构造函数传递参数。

借用构造函数

在子类构造函数的内部调用超类构造函数。

/*借用构造函数*/
function SuperType(){
    this.color = ["red","blue","green"];
}
function SubType(){
    SuperType.call(this,"...");
}
var instance1 = new SubType();
instance1.color.push("Black");
console.log(instance1.color); //["red", "blue", "green", "Black"]

var instance2 = new SubType();
console.log(instance2.color);  //["red", "blue", "green"]

优势:在子类构造函数中向超累构造函数传递参数。

缺点:函数复用无从谈起。

组合继承

伪经典继承,将原型链和借用构造函数的技术组合到一起。

使用原型链实现对原型属性和方法的集成,而通过借用构造函数来实现对实例属性的集成。

缺点:会两次调用超类型构造函数。一次在创建子对象原型的时候,另一次是在子类型构造函数内部。

子类型会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时从写这些属性。

/*组合继承*/
 function SuperType(name){
     this.name = name;
     this.color = ["red","blue","green"];
 }
 SuperType.prototype.sayName = function(){
     console.log(this.name);
 }

 function SubType(name,age){
     SuperType.call(this,name); //第二次调用SuperType(),覆盖父类的实例属性

     this.age = age;
 }

 SubType.prototype = new SuperType();//第一次调用SuperType()
 SubType.prototype.constructor = SubType;
 SubType.prototype.sayAge = function(){
     console.log(this.age);
 }
 var ins = new SuperType("huang");
 ins.sayName();
 console.log(ins);
 var instance1 = new SubType("Nicholas",29);
 instance1.color.push("black");
 console.log(instance1);
 instance1.sayName();
 instance1.sayAge();

 var instance2 = new SubType("Greg",27);
 console.log(instance2);
 instance2.sayName();
 instance2.sayAge();

原型式继承

借助原型可以基于已有的对象创建新对象,同时还不想必因此创建自定义类型。

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

你必须有一个对象可以作为另一个对象的基础。

function object(o){
     function F(){}
     F.prototype = o;
     return new F();
 }
var person = {
     name:"Nicholas",
     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");
 console.log(anotherPerson);
 console.log(yetAnotherPerson);
 console.log(person);

ECMAScript 5中,新增Object.create()方法规范了原型式继承

var person = {
     name:"Nicholas",
     friends :["shelby","Court","Van"]
 };

 var anotherPerson = Object.create(person);
 anotherPerson.name = "Greg";
 anotherPerson.friends.push("rob");

 var yetAnotherPerson = Object.create(person);
 yetAnotherPerson.name = "Linda";
 yetAnotherPerson.friends.push("Barbie");
 console.log(anotherPerson);
 console.log(yetAnotherPerson);
 console.log(person);

缺点:包含引用类型的属性始终都会共享相应的值。

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

function createAnother(original){
     var clone  = object(original);
     clone.syahi = function(){
         console.log("hi");
     }
     return clone;
 }

为对象添加函数,不能复用而降低效率。

寄生组合式继承

由于组合模式的缺点,两次调用超类的构造函数。

寄生组合模式:借用构造函数来继承属性,通过原型链的混成形式来继承方法。

基本思想:不必为指定子类型的原型而调用超类型的构造函数,我们所需要的就是超类型的一个副本而已。

/*寄生组合式模式*/
 function inheritPrototype(subType,superType){
     var prototype = Object(superType.prototype);//创建对象
     prototype.constructor = subType;//增强对象
     subType.prototype = prototype; //指定对象
 }

 function SuperType(name){
     this.name = name;
     this.color = ["red","blue","green"];
 }

 SuperType.prototype.syaName = 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);
 }

 var ins = new SuperType("huang");
 ins.sayName();
 console.log(ins);
 var instance1 = new SubType("Nicholas",29);
 instance1.color.push("black");
 console.log(instance1);
 instance1.sayName();
 instance1.sayAge();

 var instance2 = new SubType("Greg",27);
 console.log(instance2);
 instance2.sayName();
 instance2.sayAge();
文章目录
  1. 1. 理解对象
    1. 1.1. 属性类型
      1. 1.1.1. 数据属性
      2. 1.1.2. 访问器属性
    2. 1.2. 定义多个属性
    3. 1.3. 读取属性的特征
  2. 2. 创建对象
    1. 2.1. Object构造函数
    2. 2.2. 对象字面值
    3. 2.3. 工厂模式
    4. 2.4. 构造函数模式
    5. 2.5. 原型模式
      1. 2.5.1. constructor构造函数
      2. 2.5.2. isPrototypeOf() 确定对象之间是否存在原型的关系
      3. 2.5.3. Object.getPrototypeOf() 返回实际对象的原型
      4. 2.5.4. hasOwnProperty() 检测一个属性是否属于实例中,还是存在与原型中。只在给定属性存在于对象实例中时,返回true
      5. 2.5.5. getOwnPropertyDescriptor() 只能用于实例属性,要取得原型对象的描述符,必须直接在原型对象上调用getOwnPropertyDescriptor()方法
    6. 2.6. 原型与in操作符
      1. 2.6.1. in 操作符
      2. 2.6.2. Object.keys()
      3. 2.6.3. Object.getOwnPropertyNames()
    7. 2.7. 更简单的原型语法
      1. 2.7.1. 原型链式动态
      2. 2.7.2. 元素对象的原型
    8. 2.8. 原型对象的问题
    9. 2.9. 组合使用构造函数模式和原型模式
    10. 2.10. 动态原型模式
    11. 2.11. 寄生构造函数模式
    12. 2.12. 稳妥构造函数模式
  3. 3. 继承
    1. 3.1. 原型链
      1. 3.1.1. 确定原型和实例的关系
      2. 3.1.2. instanceof操作符
      3. 3.1.3. isPrototypeOf()方法
    2. 3.2. 借用构造函数
    3. 3.3. 组合继承
    4. 3.4. 原型式继承
    5. 3.5. 寄生式继承
    6. 3.6. 寄生组合式继承
,