第六章 面向对象的程序设计

6.1 理解对象

6.1.1 属性类型
ECMAScript 中有两种属性:数据属性和访问器属性。

  1. 数据属性
    数据属性包含一个数据值的位置。在这个位置可以读取和写入值。
数据属性值 默认值
[ [ configurable ] ] 表示内否通过delete 删除属性,从而重新定义属性。 true
[ [ enumerable ] ] 表示内否通过 for-in 循环返回属性。 true
[ [ writable ] ] 表示内否修改属性的值 true
[ [ value ] ] 包含这个属性的数据值 true

要想修改属性默认的特性,必须使用 ECMAScript 5 的Object.defineProperty()方法。

  • Object.defineProperty()
    这个方法接收3个参数:属性所在的对象,属性的名字和一个描述符对象。其中描述符对象的属性必须是:Configurable 、Enumerable 、Writable 、Value 。设置其中一个或多个的值。
var a = {   name : "ABC", age : 18, };
Object.defineProperty(a,"name",{
    writable:false //设置不可修改属性的值
});
console.log(a.name);  //  ABC
a.name="CBA";
console.log(a.name); // ABC
  1. 访问器属性
    访问器属性有4个特性
数据属性值 默认值
[ [ configurable ] ] 表示内否通过delete 删除属性,从而重新定义属性。 true
[ [ enumerable ] ] 表示内否通过 for-in 循环返回属性。 true
[ [ get ] ] 在读取属性时调用的函数 undefined
[ [ set ] ] 在写入属性时调用的函数 undefined

访问器属性不能这定义,必须使用Object.defineProperty() 来定义。

var a = { name : "ABC", _age : 18, m : 1};
Object.defineProperty(a,"age",{
    get: function(){
        return this._age;
    },
    set : function(newValue){
        if(newValue > this._age){
            this._age = newValue;
            this.m += this.age- 18
        }
    }
});
a.age = 20;
console.log(a.m);

_age 前面的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。

6.1.2 定义多个属性
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 += this._year - 2004;
            }
        }
    }
});
console.log(book);//{_year: 2004, edition: 1}
book.year = 2010;
console.log(book);// {_year: 2010, edition: 7}

6.1.3 读取属性的特性
Object.getOwnPropertyDescriptor()方法可以取得给定属性的描述符。

    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 += this._year - 2004;
                }
            }
        }
    });
    var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
    alert(descriptor.value);//2004
    alert(descriptor.configurable);//false
    alert(descriptor.get);//undefined
    
    var descriptor = Object.getOwnPropertyDescriptor(book,"year");
    alert(descriptor.value);//undefined
    alert(descriptor.enumerable);//false
    alert(descriptor.get)//function

6.2 创建对象

6.2.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 p1 = createPerson("A",18,"TH");
var p2 = createPerson("B",81,"DT");

6.2.2 构造函数模式
创建自定义构造函数,从而定义自定义对象类型的属性和方法。构造函数首字母大写。前面的例子可如下重写:

 function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName = function(){
        alert(this.name);
    };
}
var p1 = new Person("A",18,"TH");
var p2 = new Person("B",81,"DT");
  • 1.将构造函数当作其他函数
    使用new操作符调用的,就可以作为构造函数,否则和普通函数一样。前面例子演示:
function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName = function(){
        alert(this.name);
    }
}
var p1 = new Person("ABC",18,"TH");  // 构造函数使用
p1.sayName();
    
Person("ABC",29,"DT");// 普通函数使用
window.sayName();
    
var o = new Object();
Person.call(o,"ABC",18,"DT");// 在另一个作用域使用
o.sayName();
  • 2.构造函数的问题
    会造成不同作用域链和标识符解析
this.sayName = function(){
        alert(this.name);
    };
//                等于
this.sayName = new Function("alert(this.name)");

通过使用原型模型可以解决。
6.2.3 原型模型
每一个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象。使用原型对象的好处就是可以让所有对象实例共享它所包含的的属性和方法。

function Person(){}
Person.prototype.name = "ABC";
Person.prototype.age = 18;
Person.prototype.job = "DT";
Person.prototype.sayName = function (){
    alert(this.name);
}
var p1 = new Person();  p1.sayName();//ABC
var p2 = new Person();  p2.sayName();//ABC
alert(p1.sayName == p2.sayName); // true
  • 1.理解原型

isPrototypeOf()
检测一个对象是否是另一个对象的原型(或者处于原型链中)。

alert(Person.prototype.isPrototypeOf(p1)); // true
alert(Person.prototype.isPrototypeOf(p2));  //true

Object.getPrototypeOf()
此方法可以获取指定对象的原型对象。

alert(Object.getPrototypeOf(p1) == Person.prototype); // true
p1.name = "QQQ";
alert(Object.getPrototypeOf(p1).name); //ABC

当为对象实例添加一个属性时,这个属性会屏蔽原型对象中保持的同名属性,但不会修改原型对象中的同名属性值。
不过可以使用 delete 操作符删除实例对象属性,从而恢复重新访问原型中的属性。

function Person()  { }
Person.prototype.name = "ABC";
var p1 = new Person();  
var p2 = new Person();
p1.name="CBA";
alert(p1.name); // CBA
alert(p2.name) // ABC
delete p1.name;
console.log(p1.name);//ABC

hasOwnProperty()方法 可以检测一个属性是存在实例中还是存在于原型中。
存在原型中返回 false,存在实例中返回 true。

function Person(){}
Person.prototype.name = "ABC";
var p1 = new Person();
console.log(p1.name);//ABC
console.log(p1.hasOwnProperty("name"));//存在原型中 返回false
p1.name="CBA";
console.log(p1.name);
console.log(p1.hasOwnProperty("name"));//存在实例中 返回true
  • 2 原型与in操作符
    in 操作符会在通过对象能够访问给定属性时返回true 无论属性存在于实例中还是原型中。
function Person(){}
Person.prototype.name = "ABC";
var p1 = new Person();

console.log("name" in p1); //来自原型
console.log(p1.hasOwnProperty("name"));//flse
    
p1.name="DEF";
console.log("name" in p1);//来自实例
console.log(p1.hasOwnProperty("name"));//true

若同时使用hasOwnProperty()方法和in操作符,就看确定该属性到底是存在与对象中,还是存在与原型中。封装一个函数调用来确定。 hasPrototypeProperty(object,name)

function Person(){}
Person.prototype.name = "ABC";
Person.prototype.age = 18;
Person.prototype.job = "DT";
    
var p1 = new Person();
    
console.log(hasPrototypeProperty(p1,"name"));//true
    
p1.name="DEF";
    
console.log(hasPrototypeProperty(p1,"name"));
    
function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name) && (name in object);
}

Object.keys()方法
这个方法接收一个对象作为参数,返回一个包含可枚举属性的字符串数组

var keys = Object.keys(Person.prototype);
alert(keys); // "name,age,job"
  • 3.更简单的原型语句
    将 .prototype 以对象字面量形式创建新的对象。但 constructor 不再指向Person,指向Object构造函数。
function Person (){}
Person.prototype = {
  name:"ABC",
  age:18,
  job:"DT",
constructor:"Person" //用此方法可以特意设定回Person
};
  • 4.原型的动态链
    通过原型修改属性,并不会使实例对象发生错误,但是重写原型对象属性,则实例对象应用属性会发生错误。
 var p1 = new Person();
function Person(){}
//原型修改不会出错  
Person.prototype.sayHi = function (){
    alert("hi!");
};

/*  重写原型对象
Person.prototype = {
    name:"ABC",
    age:18,
    job:"DT",
    sayHi:function(){
        alert("hi!");
    }
}
*/
p1.sayHi();
  • 5.原生对象的原型
    原生的引用类型也可以创建自定义类型 (String,Array,Object ...)
 String.prototype.startsWith = fucntion(text){
  return this.indexOf(text);
}
var str = "HELLO WORLD!";
alert(str.startsWith("HELLO"));  // true

6.2.4 组合使用构造函数模式和原型模式
重写上面的例子

function Person (name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["KKK","JJJ"];
}

Person.prototype = {
    constructor : Person,
    sayName : function (){
        alert(this.name);
    }
}
var p1 = new Person("AAA",18,"DT");
var p2 = new Person("BBB",20,"TH");

p1.friends.push("MMM");
console.log(p1.friends); // KKK JJJ MMM
console.log(p2.friends); // KKK JJJ
console.log(p1.friends === p2.friends);//false
console.log(p1.sayName === p2.sayName);//true
  • 6.2.5 动态原型模式
  • 6.2.6 寄生构造函数模式
function SpecialArray(){
    var value = new Array();

    value.push.apply(value,arguments);

    value.toPipedString = function(){
        return this.join("|");
    };
    return value;

}
var color = new SpecialArray("red","black","blue");
console.log(color.toPipedString()); //red|black|blue
  • 6.2.7 稳妥构造函数
    一是新创建的对象不引用this,二不使用new操作符调用构造函数。
function Person(name,age,job){
    //创建要返回到函数
    var  o = new Object();
    //定义私有变量和函数

    //添加方法
    o.sayName = function (){
        alert(name);
    }
    //返回对象
    return o;
}
var p1 = Person("aaa",18,"DT");
console.log(p1.name);// undefined
p1.sayName(); // aaa

6.3 继承

  • 6.3.1 原型链
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();
alert(instance.getSuperValue()); //true

1.别忘记默认的原型
2.确定原型和实例的关系
通过两种方式来确定原型和实例之间的关系
instanceof
xxx 是Object类型的实例

alert( xxx instanceof Object) //true

isPrototypeOf()
XXX是原型链所派生出的实例的原型s

alert(Object.prototype.isPrototypeOf(XXX)); //true

3.谨慎的定义方法
在通过原型链实现继承时,不能使用对象字面量创建方法,这样会重写原型链的。

  • 6.3.2 借用构造函数
    通过apply() 和call() 方法可以在新创建的对象是执行构造函数
function SuperType(){
    this.colors = ["red","blue"];
}
function SubType(){
    //继承了SuperType
    SuperType.call(this);
}
var temp = new SubType();
temp.colors.push("black");
console.log(temp.colors);//red,blue,black
    
var temp2 = new SubType();
console.log(temp2.colors);//red,blue

传递参数

function SuperType(name){
    this.name=name;
}
function SubType(){
//继承SuperType 同事传递参数
    SuperType.call(this,"ABC");
    this.age = 18;
}
var temp = new SubType();
console.log(temp.name);
console.log(temp.age);
  • 6.3.3 组合继承
    将原型链和借用构造函数的技术组合到一起
    组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点。
function SuperType(name){
    this.name = name;
    this.colors = ["red","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);
}
var temp = new SubType("ABC",18);
temp.colors.push("black");
console.log(temp.colors);// red,blue,black
temp.sayName();//ABC
temp.sayAge();//18
    
var temp2 = new SubType("DEF",20);
console.log(temp2.colors);//red,blue
temp2.sayName();//DEF
temp2.sayAge();//20
  • 6.3.4 原型式继承
    通过Object.create()方法规范了原型式继承
var person = {
    name:"ABC",
    friends:["EEE","DDD","FFF"]
};
    
var anotherPerson = Object.create(person);
anotherPerson.name = "111";
anotherPerson.friends.push("GGG");
    
alert(person.friends); // EEE,DDD,FFF,GGG

Object.create()方法第二个参数格式如下,指定的任何属性都会覆盖原型对象上的同名属性。

var person = {
    name:"ABC",
    friends:["EEE","DDD","FFF"]
};
    
var anotherPerson = Object.create(person,{
    name:{
        value:"QQQQQ"
    }
});
    
alert(person.friends); // QQQQQ
  • 6.3.5 寄生式继承

你可能感兴趣的:(第六章 面向对象的程序设计)