码迷,mamicode.com
首页 > 编程语言 > 详细

《javascript高级程序设计》笔记(六)

时间:2014-09-26 02:40:28      阅读:434      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   color   io   os   使用   java   

 

一、理解对象

1.属性类型

①数据类型

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

[[Configurable]]:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为true。

[[Enumerable]]:能否通过for-in循环返回属性,默认为true。

[[Writable]]:能否修改属性的值。默认为true。

[[Value]]:包含这个属性的数据值。默认值是undefined。

 

要修改属性默认的特性必须使用——Object.defineProperty()方法接收三个参数:属性所在对象、属性名和一个描述符对象。

        var person = {};
        Object.defineProperty(person, "name", {
            writable: false,
            value: "Nicholas"
        });
        
        alert(person.name);
        person.name = "Michael";
        alert(person.name);

 

把configurable设置为false,表示不能从对象中删除属性。如果对这个属性调用delete,在非严格模式下什么也不会发生,在严格模式下会出错。一旦把属性定义为不可配置的,就再也不能把它变回可配置。再调用Object.defineProperty()方法修改writable以外的特性都会出错。

 

②访问器类型

不包含数据值,它们包含一对getter和setter函数。

[[Configurable]]:能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为true。

[[Enumerable]]:能否通过for-in循环返回属性,默认为true。

[[Get]]:读取属性时调用的函数,默认值为undefined。

[[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;
                
                }
            }
        });
        
        book.year = 2005;
        alert(book.edition);   //2

_表示只能通过对象方法访问的属性。getter函数返回_year的值,setter函数确定正确的版本。

只指定getter意味着属性不能写,只指定setter函数的属性不能读。

支持的浏览器IE9+、Firefox4+、Sarifi5+、Opera12+和Chrome。

2.定义多个属性

Object.defineProperties()通过描述符一次定义多个属性。参数:第一个对象要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性对应。

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

支持的浏览器IE9+、Firefox4+、Sarifi5+、Opera12+和Chrome。

3.读取属性的特性

Object.getOwnPropertyDescriptor():取得给定属性的描述符。两个参数:属性所在的对象、要读取其描述符的属性名称。

        var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
        alert(descriptor.value);          //2004
        alert(descriptor.configurable);   //false
        alert(typeof descriptor.get);     //"undefined"
        
        var descriptor = Object.getOwnPropertyDescriptor(book, "year");
        alert(descriptor.value);          //undefined
        alert(descriptor.enumerable);     //false
        alert(typeof descriptor.get);     //"function"

支持的浏览器IE9+、Firefox4+、Sarifi5+、Opera12+和Chrome。

 

二、创建对象

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 person2 = createPerson("Greg", 27, "Doctor");

 

2.构造函数模式

 function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
                alert(this.name);
            };    
        }
var person2 = new Person("Greg", 27, "Doctor");

没有显式地创造对象;

直接将属性和方法赋给this对象;

没有return语句。

构造函数始终应该以大写字母开头。

 

alert(person2.constructor == Person);  //true

对象都有一个constructor(构造函数)属性,该属性指向Person

 

  alert(person1 instanceof Object);  //true
  alert(person1 instanceof Person);  //true

对象既是Object的实例,也是Person的实例

 

①将构造函数当作函数

构造函数与其他函数的唯一不同在于调用它们的方式不同。

任何函数,只要通过new操作符来调用,它就可以作为构造函数。

②构造函数的问题

主要问题:每个方法都要在每个实例上重新创建一次。

不同实例上的同名函数是不相等的。

//把函数定义转移到构造函数外部
function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = sayName;
        }
        
        function sayName(){
            alert(this.name);
        }

sayName包含的是指向函数的指针,对象共享了全全局作用域中定义的同一个函数。

问题:全局作用域定义的函数值被某个对象调用;如果对象要定义很多方法,那么就要定义很多全局函数。

3.原型模式

每个函数都有prototype属性,它是一个指针,对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

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

好处:让所有对象实例共享它所包含的属性和方法,将对象实例的信息添加到原型对象。

 function Person(){
  }
        
 Person.prototype.name = "Nicholas";
 Person.prototype.age = 29;
 Person.prototype.job = "Software Engineer";
 Person.prototype.sayName = function(){
     alert(this.name);
 };
        
 var person1 = new Person();

 

①理解原型对象

创建一个函数就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。

原型对象会获得constructor(构造函数)对象,这个属性包含指向prototype属性的指针。

调用构造函数创建新实例后,实例内包含指向构造函数的原型对象的指针[[Prototyoe]]。

  bubuko.com,布布扣

isPrototypeOf()、getPrototypeOf()

调用person1.sayName时,先看实例person1有没sayName属性,没有则看person1的原型有没sayName属性

 

function(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.sayName = functon(){
   alert(this.name)
};

var person1 = new Person;
var person2 = new Person;

person1.name = "Grey";

 alert(person1.name);  //Grey 来自实例
 alert(person2.name);  //Nicholas 来自原型

delete(person1.name);
 alert(person1.name);  //Nicholas

实例中添加一个属性,这个属性会屏蔽原型对象中保存的同名属性,不会被修改。

使用delete操作符可以删除实例属性,能重新访问原型中的属性。

 

hasOwnPrototyoe()方法:检测一个属性是存在于实例还是原型。给定属性存在于对象实例中返回true。

 

alert(person1.hasOwnProperty("name")); //false

person1.name = "Grey";
alert(person1.hasOwnProperty("name")); //true 来自实例

 

②原型与 in 操作符

in操作符:单独使用时通过对象访问给定属性时返回true,无论该属性是在原型还是实例,也可以在for-in循环中使用。

//.......

var person1 = new Person();
alert(person1.hasOwnproperty("name")); //false
alert("name" in person1);  //true

person1.name = "nima";
alert(person1.hasOwnproperty("name")); //true
alert("name" in person1);  //true

 

 

同时使用in操作符和hasOwnproperty()方法可以确定属性在原型还是在对象。

function  hasPropertypeProperty(object, name){
   return !object.hasOwnProperty(name) &&( name in object);
}

 

 

for-in循环返回的是所以能通过对象范围的、可枚举的属性,包括存在于实例和原型的属性。屏蔽了原型中不可枚举([[Enumerable]]标记为false)属性的实例属性也会在for-in返回。因为所有开发人员定义的属性都是可枚举的——IE8和之前的版本例外。

这些属性包括hasOwnproperty()、propertyIsEnumerable()、toLocaleString()、toString()和valueOf()。

 

Object.keys()方法:取得对象上所有可枚举的实例属性。这个方法接收一个对象为参数,返回一个包含所有可枚举属性的字符串数组

var keys = Object.keys(Person.prototype);
alert(keys);   //"name,age,job,sayName"

 

Person的实例调用

var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys);    //"name,age"

 

 

Object.getOwnPropertyNames():取得所有实例属性

var keys = Object.getOwnpropertyNames(Person.Prototype);
alert(keys); //"constructor,name,age,job,sayName";

 

 

③更简单的原型语法

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

 

这样完全重新了默认的Prototype,constructor属性不再指向Person函数,而指向Object构造函数。

        var friend = new Person();
        
        alert(friend instanceof Object);  //true
        alert(friend instanceof Person);  //true
        alert(friend.constructor == Person);  //false
        alert(friend.constructor == Object);  //true

 

 

如果constructor值很重要,可以这样设置

 Person.prototype = {
             constructor : Person;
            }
        };

 

按会导致[[Enumerable]]被设置为true,兼容ECMAScript 5可以用Object.defineProperty()。

Object.defineProperty(Person.prototype, "constructor",{
      enumerable:  false;
      value: Person
});

 

 

④原型的动态性

即使先创建实例后修改原型,我们队原型对象所做的修改都能在实例反应,因为实例与原型的连接只是指针。

 

但如果重写整个原型对象会切断构造函数与最初原型之间的联系。

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

 

 

bubuko.com,布布扣

 

⑤原生对象的原型

所有原生引用类型(Object、Array、String等)都在其构造函数的原型上定义了方法。如Array.prototype 中有sort()方法,String.prototype 中有subString()方法。

通过原生对象的原型,不仅可以取得所有默认方法的引用,还可以定义新方法和添加方法。

 

⑥原型对象的问题

原型模式最大的问题是其共享的本质。对于包含引用类型值的属性来说问题比较突出。

        function Person(){
        }
        
        Person.prototype = {
            friends : ["Shelby", "Court"],
        };
        
        var person1 = new Person();
        var person2 = new Person();
        
        person1.friends.push("Van");
        
        alert(person1.friends);    //"Shelby,Court,Van"
        alert(person2.friends);    //"Shelby,Court,Van"
        alert(person1.friends === person2.friends);  //true

 

friend数组存在于Person.prototype而非person。

同时,它省略了为构造函数传递初始化参数这一环节,所有实例在默认情况下都取得相同的属性值。

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 () { alert(this.name); } }; var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true

 

5.动态原型模式

把所有信息封装在构造函数,通过在构造函数中初始化原型(在有必要的情况),保持同时使用原型和构造函数的优点。

可以通过检查某个应该存在的方法是否有效来决定是否需要初始化原型。

 function Person(name, age, job){
        
            //属性
            this.name = name;
            this.age = age;
            this.job = job;
            
            //方法
            if (typeof this.sayName != "function"){
            
                Person.prototype.sayName = function(){
                    alert(this.name);
                };
                
            }
        }

只有在初次调用构造函数时才会执行创建方法的代码,只需要检查其中一个方法就可以。

还可以用Instanceof操作符检查它的类型。

不能使用对象字面量重写原型,在创建了实例的情况下重写原型会切断实例和新原型的联系。

 

6.寄生构造函数模式

基本思想是创建一个函数,该函数的作用只是封装创建对象的代码,然后返回新创建的对象。

除了使用new操作符并把使用的函数叫做构造函数以外,和工厂模式一样。

这个模式可以在特殊情况下用来为对象构造函数。

构造函数返回的对象和在构造函数外创建的对象没什么不同,不能用isinstanceof操作符确定函数类型,建议可以用其他模式时不用这个模式。

7.稳妥构造函数模式

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

对象与构造函数之间没有关系,instanceof操作符对这种对象没有意义。

三、继承

1.原型链

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。

        function SuperType(){
            this.property = true;
        }
        
        SuperType.prototype.getSuperValue = function(){
            return this.property;
        };
        
        function SubType(){
            this.subproperty = false;
        }
        
        //inherit from SuperType
        SubType.prototype = new SuperType();
        
        SubType.prototype.getSubValue = function (){
            return this.subproperty;
        };

 

bubuko.com,布布扣

SuperType的实例不仅具有作为SuperType的实例所具有的全部属性和方法,而且其内部还有指向SuperType的原型的指针。getSuperValue()方法仍然在SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么prototype当然位于该实例。

①别忘记默认的原型

所有函数的默认原型都是Object的实例。

bubuko.com,布布扣

②确定原型和实例的关系

instancof操作符测试实例与原型链中出现过的构造函数

        alert(instance instanceof Object);      //true
        alert(instance instanceof SuperType);   //true
        alert(instance instanceof SubType);     //true

 

 

只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。

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

 

 

③谨慎地定义方法

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

        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;
        };
        
        //重写超类型中的方法
        SubType.prototype.getSuperValue = function (){
            return false;
        };

        var instance = new SubType();
        alert(instance.getSuperValue());   //false
        
        var instance2 = new SuperType();
        alert(nima.getSuperValue());      //true

 

 

通过SubType的实例调用getSuperValue()调用的是重新定义的方法,通过SuperType的实例调用getSuperValue()还会调用原来的方法。必须用SuperType替换原型后再定义这两个方法。

 

通过原型链实现继承,不能用对象字面量创建原型方法,这样会重写原型链。

 

④原型链的问题

包含引用类型值的原型属性会被所有实例共享,原先的实例属性也会变成现在的原型属性。

        //继承了 SuperType
        SubType.prototype = new SuperType();

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

 

 

2.借用构造函数

在子类型构造函数的内部调用超类型构造函数,通过apply()和call()方法在(将来)新创建的对象上执行构造函数。

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

        function SubType(){  
            //继承自SuperType
            SuperType.call(this);
        }

 

①传递参数

相对于原型链,借用构造函数的优势是可以在子类型构造函数中向超类型构造函数传递参数。

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

        function SubType(){  
            //继承了 SuperType,同时传递了参数
            SuperType.call(this, "Nicholas");
            
            //instance property
            this.age = 29;
        }

 

②借用构造函数的问题

方法都在构造函数中定义,不能做到函数复用。而且在超类型的原型中定义的方法,对子类型而言也是不可见的。

3.组合继承

使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实力属性的继承。

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

        function SubType(name, age){  
//继承属性 SuperType.call(
this, name); this.age = age; }
//继承方法 SubType.prototype
= new SuperType(); SubType.prototype.sayAge = function(){ alert(this.age); };

SubType构造函数在调用SuperType构造函数时传入name参数,又定义了自己的属性age。然后将SuperType的实例复制给SubType的原型,在该原型上定义方法sayAge()。

instanceof和isPrototypeOf()能用于基于组合继承创建的对象。

4.原型式继承

在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。

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

在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上,object()对传入其中的对象执行了一次浅复制。

 

        var person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };
        
        var anotherPerson = object(person);

必须有一个对象可以作为另一个对象的基础,把它传递给object()函数。这里我们把person传入到object()函数,得到的新对象将person作为原型,所以它的原型包括一个基本类型值属性和一个引用类型值属性。

 

ECMAScript5新增Object.create()方法接收两个参数,一个作为新对象原型的对象,(可选)一个作为新对象定义额外属性的对象。在传入一个参数时与object()方法相同。

传入第二个参数时,会覆盖原型对象上的同名属性。

     var anotherPerson = Object.create(person, {
            name: {
                value: "Greg"
            }
        });

 只是想让一个对象鱼另一个对象保持类似的情况下,原型式继承可以胜任。包含引用类型值的属性始终会共享相应的值。

 

5.寄生式继承

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

function createAnother(original){
     var clone = object(original);      //通过调用函数创建一个新对象
     clone.sayHi = function(){          //以某种方法增强对象
         alert("hi");
     };
     return clone;                      //返回对象
}

不能做到函数复用

6.寄生组合式继承

组合继承最大的问题是无论什么情况下,都会调用两次超类型函数;一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

子类型最终会包含超类型对象的所有实例属性,但我们不得不在调用子类型构造函数时候重写这些属性。

 

在调用SubType构造函数时,会再一次调用SuperType构造函数,又在新对象上创建了实例属性name和colors。这两个属性屏蔽了原型中的两个同名属性。

 

寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。其本质是使用寄生式继承来继承超类型的原型,然后将结果指定给子类型的原型。

function inheritPrototype(subType, SuperType){
       var prototype = object(subType.prototype);   //创建对象
       prototype.constructor = subType;             //增强对象
       subType.prototype = prototype;               //指定对象
}

第一步,创建超类型原型的一个副本。第二步为创建的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性,最后一步将新创建的对象赋值给子类型的原型。

 替换掉前面例子中为子类型原型赋值语句(SubType.call(this, name);)。

《javascript高级程序设计》笔记(六)

标签:des   style   blog   http   color   io   os   使用   java   

原文地址:http://www.cnblogs.com/surahe/p/3981283.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!