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

《JavaScript高级程序设计》第六章【面向对象的程序设计】 包括对象、创建对象、继承

时间:2016-08-16 00:10:02      阅读:165      评论:0      收藏:0      [点我收藏+]

标签:

一、理解对象

ECMAScript中有两种属性:数据属性访问器属性

 

二、创建对象

1. 工厂模式

  使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这种方法后来被构造函数模式所取代。

 

2. 构造函数模式

  可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。但是它的每个成员都无法得到复用,包括函数。

  但是这样说好像也不准确——如果是通过一个指针指向构造函数外部的函数的话,应该算是复用?

 1         function Person(name,age){
 2             this.name = name;
 3             this.age = age;
 4             this.sayName = sayName;  //一个指向函数的指针,所以所有的实例共享同一函数
 5             this.sayAge = function(){
 6                 console.log(this.age);
 7             }
 8         }
 9         function sayName(){
10             console.log(this.name);
11         }
12         var person1 = new Person(‘Jack‘);
13         var person2 = new Person(‘Amy‘);
14         person1.sayName();
15         person2.sayName();
16         console.log(person1.sayName === person2.sayName);   //true
17         console.log(person1.sayAge == person2.sayAge);      //false

  但是这种方法:1)sayName函数在全局作用域中定义,但实际只被某个对象调用,名不副实

         2)没有封装性

 

3. 原型模式

  使用构造函数的prototype属性来指定那些应该共享的属性和方法。

 

4. 组合使用构造函数模式和原型模式【使用最广泛】

  构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的熟悉。且这种模式还支持构造函数传递参数

  

 1         function Person(name){
 2             this.name = name;
 3         }
 4         Person.prototype = {
 5             constructor : Person,       //因为这里通过对象字面量重写了整个原型对象,但constructor会指向Object。所以要特意设置
 6             sayName : function(){
 7                 console.log(this.name); 
 8             }
 9         }
10         var person1 = new Person(‘Nick‘);
11         var person2 = new Person(‘Amy‘);
12         console.log(person1.sayName == person2.sayName);        //true

 

5. 动态原型模式

1         function Person(name){
2             this.name = name;
3             if(typeof this.sayName != "function"){          //这段代码只会在在初次调用构造函数时才会执行
4                 Person.prototype.sayName = function(){
5                     console.log(this.name);
6                 }
7             }
8         }
9         var person = new Person(‘Jack‘);

  可以使用instanceof操作符来确定实例类型  

  注意:使用动态函数模型时,不能使用对象字面量重写原型。

     在已经创建了实例的情况下重写原型,就会切断现有实例和新原型之间的关系。

     举个栗子:

 1         function Person(name){
 2             this.name = name;
 3             if(typeof this.sayName != "function"){          //这段代码只会在在初次调用构造函数时才会执行
 4                 Person.prototype.sayName = function(){
 5                     console.log(this.name);
 6                 }
 7             }
 8         }
 9         var person = new Person(‘Jack‘);
10         Person.prototype = {
11             sayHi : function(){
12                 console.log("hi");
13             }
14         }
15         person.sayName();       //Jack
16         //person.sayHi();         //Uncaught TypeError: person.sayHi is not a function
17 
18         var person2 = new Person(‘Amy‘);
19         person2.sayHi();        //hi
20         person2.sayName();      //Amy

  打印出person和person2:

       技术分享

 

  这里我的理解是:调用构造函数会为实例增加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象,则会切断构造函数与最初原型之间的关系。因为实例中的指针只会指向原型,而不指向构造函数,此时修改的是构造函数的原型,而之前创建的实例仍然指向之前的原型对象。故其仍然用于sayName()函数。然而,为什么后来创建的实例person2也会拥有sayName()函数呢?不是之前的构建已经切断了吗?这里我猜测原因是因为在创建实例person2时,if逻辑检测到Person中没有sayName()函数,于是又增加了这样一个函数。为了验证猜想,我在if逻辑里打印一句话,这样只要进入循环就会打印出来这句话,果然打印了两次,猜想得证。

 

6. 寄生构造函数模式

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

  常用于为对象创建构造函数。

  一个栗子:创建一个具有额外方法的特殊数组

  

 1         function SpecialArray(){
 2             //创建数组
 3             var values = new Array();
 4 
 5             //添加值
 6             values.push.apply(values, arguments);       //这里比较疑惑的是为什么要用apply,既然values是一个数组,那么直接调用push不可以吗?
 7             //values.push(arguments);                 //[object Arguments]
 8 
 9             //添加方法
10             values.toPipedString = function(){
11                 return this.join("|");
12             };
13 
14             //返回数组
15             return values;
16         }
17         var color = new SpecialArray("red", "blue", "green");
18         console.log(color.toPipedString());     //red|blue|green

  上述代码中遇到了一个问题:如第6.7行所示,为什么要用apply而不能直接使values.push(arguments)呢?

  ——换成直接使用push后输出是[object Arguments]。然后查了下发现arguments果然是一个对象,那为什么apply中可以直接用?猜想应当是apply内部实现对arguments进行了解析。

 

  关于寄生构造函数模式,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没什么不同。为此,不能依赖instanceof操作符来确定对象类型

  所以,可以用其他,不要用这个方法。

 

7. 稳妥构造函数模式

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

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

            return o;
        }
        var person = Person("nick");
        person.sayName();   //nick

  这种方法与上一个方法的区别在于:新创建对象的实例方法不引用this,不使用new操作符调用构造函数。

  这种方法除了调用sayName()函数外,没有别的办法可以访问到其数据成员,所以具有安全性。

  instanceof操作符对这种方法也无效

 

三、继承

  两种继承方式:接口继承和实现继承。ECMAScript只支持实现继承,且其实现继承主要是依靠原型链来实现的

1. 原型链

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

  构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,如果让原型对象等于另一个对象的实例,则原型对象将包含一个指向另一个原型的指针,另一个原型中也包含一个指向另一个构造函数的指针。(这都是什么鬼!)

  实现原型链模式:

 

        function Parent(){
            this.name = "parent";
        }
        Parent.prototype.getName = function(){
            return this.name;
        }
        function Child(){
            this.subName = ‘child‘;
        }
        //继承了Parent
        Child.prototype = new Parent();
        Child.prototype.getSubName = function(){
            return this.subName;
        };
        var child = new Child();
        console.log(child.getName());   //parent
        console.log(child.getSubName());    //child

        //两种确定原型和实例的关系的方法
        //1)instanceof
        console.log(child instanceof Object);       //true
        console.log(child instanceof Parent);       //true
        console.log(child instanceof Child);        //true

        //isPrototypeOf()
        console.log(Object.prototype.isPrototypeOf(child));     //true
        console.log(Parent.prototype.isPrototypeOf(child));     //true
        console.log(Child.prototype.isPrototypeOf(child));      //true

  子类型有时候需要重写超类中的某个方法,或者需要添加超类中不存在的某个方法,给原型添加方法的代码一定要放到替换原型的语句之后。否则,子类的方法会被覆盖。因为原型指针指向了父类的原型。

 1         function Parent(name){
 2             this.name = name;
 3         }
 4         Parent.prototype.sayName = function(){
 5             console.log("Parent: my name is " + this.name);
 6         }
 7         function Child(name,age){
 8             this.name = name;
 9         }
10         Child.prototype.sayName = function(){
11             console.log("Child: my name is "+ this.name);
12         };
13         Child.prototype = new Parent();
14         var child = new Child(‘Amy‘);
15 
16         child.sayName();        //Parent: my name is Amy

  原型链继承的问题:原先的实例属性也会变成原型属性。且不能向构造函数传递参数

2. 借用构造函数

  apply()和call()

3. 组合继承【最常用】

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

  instanceof和isPrototype()也能够识别基于组合继承创建的对象

 1         function Parent(name){
 2             this.name = name;
 3         }
 4         Parent.prototype.sayName = function(){
 5             console.log(this.name);
 6         }
 7         function Child(name,age){
 8             Parent.call(this,name);
 9             this.age = age;
10         }
11         Child.prototype = new Parent();
12         Child.prototype.sayAge = function(){
13             console.log(this.age);
14         }
15         var child = new Child(‘Jack‘,27);
16         child.sayName();    //Jack
17         child.sayAge();     //27

 

4. 原型式继承

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

  

1         function object(o){
2             function F(){}      //创建一个临时的构造函数
3             F.prototype = o;    //将传入的对象作为这个构造函数的原型
4             return new F();     //返回这个临时类型的一个实例
5         }

  这种方法要求必须有一个对象可以作为另一个对象的基础。

  ECMAScript5中新增了一个函数实现了原型式继承:Object.create()。

  Object.create()函数接收两个参数:一个用于新对象原型,一个为新对象定义额外属性的对象。

  这种方法也可能会使引用类型值的属性共享

 1         var Person = {
 2             name: "Nick",
 3             friends: [1,2,3,4]
 4         };
 5         var anotherPerson = Object.create(Person);
 6         anotherPerson.name = "Joe";
 7         anotherPerson.friends.push(5);
 8 
 9         var yetAnotherPerson = Object.create(Person);
10         yetAnotherPerson.name = "Amy";
11         yetAnotherPerson.friends.push(6);
12 
13         console.log(Person.friends);    //[1,2,3,4,5,6]
14 
15         var person2 = Object.create(Person, {
16             name: {
17                 value: "Lee"
18             }
19         });
20         console.log(Person.name);   //Nick
21         console.log(person2.name);  //Lee
22         console.log(person2);       //name:Nick在其原型之中。原型链会先找实例属性

 

5. 寄生式继承

  创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,然后返回对象

 1         function createAnothor(o){
 2             var clone = Object.create(o);
 3             clone.sayHi = function(){
 4                 console.log("hi");
 5             };
 6             return clone;
 7         }
 8         var person = {
 9             name : "Nick",
10         }
11         var anotherPerson = createAnothor(person);
12         anotherPerson.sayHi();

  这种模式用于主要考虑对象不是自定义类型和构造函数的情况下。

  这种方法来为对象添加函数,也不能做到函数复用。

 

6. 寄生组合式继承

  之前提到的组合函数的不足在于:无论什么情况下,都会调用两次超类型构造函数。一次是在创建子类原型的时候,一次是在子类构造函数内部。

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

  思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

 1         function inheritPrototype(Child, Parent){
 2             var prototype = Object.create(Parent.prototype);    //创建对象
 3             prototype.constructor = Child;                      //增强对象
 4             Child.prototype = prototype;                        //指定对象
 5         }
 6         function Parent(name){
 7             this.name = name;
 8         }
 9         Parent.prototype.sayName = function(){
10             console.log(this.name);
11         };
12         function Child(name,age){
13             Parent.call(this,name);
14             this.age = age;
15         }
16         inheritPrototype(Child,Parent);
17         Child.prototype.sayAge = function(){
18             console.log(this.age);
19         }
20         var child = new Child(‘Jack‘,29);
21         child.sayName();
22         child.sayAge();
23         console.log(child); //这时,child的__proto__中就不会有从父类继承来的name和age属性了

用寄生组合式继承打印出来的child:

技术分享

 

很干净。而如果用组合继承的话:

技术分享

可以看到其原型中有一个多余的name属性。

《JavaScript高级程序设计》第六章【面向对象的程序设计】 包括对象、创建对象、继承

标签:

原文地址:http://www.cnblogs.com/haoyijing/p/5774598.html

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