JavaScript--面向对象与原型(15)

// ECMAScript有两种开发模式:1.函数式(过程化);2.面向对象(OOP);浏览器

一 建立对象

1.普通的建立对象

1 // 建立一个对象,而后给这个对象新的属性和方法;
2     var box = new Object();              // 建立一个Object对象;
3     box.name = 'lee';                    // 建立一个name属性并赋值;
4     box.age = 100;
5     box.run = function(){                // 建立一个run()方法并返回值;
6         return this.name+this.age+'运行中...';
7     } 
8     console.log(box.run());              // 输入属性和方法的值;
9 // 缺点:想建立相似的对象,就会产生大量的代码;

2. 工厂模式建立对象

 1 // 这种方法就是为了解决实例化对象产生大量代码重复的问题;
 2     function createObject(name,age){        // 集中建立函数体;
 3         var obj = new Object;         // 函数体内建立Object;  4         obj.name = name; 
 5         obj.age = age;
 6         obj.run = function(){
 7             return this.name+this.age+"运行中...";
 8         };
 9         return obj;
10     }
11     var box1 = createObject("lee",100);     // 实例化;调用函数并传参;
12     var box2 = createObject("jack",200);    // 实例二;
13     console.log(box1.run()+box2.run());     // 实例保持相对独立;
14 // 缺点:对象与实例的识别问题;没法搞清楚它们究竟是那个对象的实例;
15     console.log(typeof box1);               // Object;

3.构造函数建立对象

 1 // ECMAScript采用构造函数(构造方法)可用来建立特定的对象;
 2     function Box(name,age){          // 构造函数模式;  3         this.name = name;           // this表明对象Box;  4         this.age = age;
 5         this.run = function(){
 6             return this.name+this.age+"运行中...";
 7         };
 8     }
 9     var box1 = new Box("lee",100);         // 要建立对象的实例必须用new操做符;
10     var box2 = new Box("jack",200);        // box1和box2都是Box对象的实例;
11     console.log(box1 instanceof Box);      // true;很清晰的识别box1从属于Box;
12 // 使用构造函数,即解决了重复实例化的问题,有解决了对象识别的问题;
1 // 使用构造函数与工厂模式不一样之处:
2 // (1).构造函数方法没有显示的建立对象(new Object);
3 // (2).直接将属性和方法赋值给this对象;
4 // (3).没有return语句;
1 // 构造函数规范:
2 // (1).函数名(function Box)和实例化构造名(new Box)相同且大写;
3 // (2).经过构造函数建立实例对象,必须使用new运算符;
1 // 构造函数和普通函数的区别:
2     var box = new Box('lee',100);               // 构造模式调用;
3     Box('lee',200);                             // 普通模式调用,无效;
4 
5     var o = new Object();
6     Box.call(o,'jack',200);                     // 对象冒充调用;
7     // 将Box对象做用域扩充到对象o;Box()方法的运行环境已经变成了对象o里;
1 // 构造函数的问题:
2 // 使用构造函数建立每一个实例的时候,构造函数里的方法都要在每一个实例上从新建立一遍;
3 // 由于ECMAScript中的函数是对象,所以每定义一个函数,也就是实例化了一个对象;
4 // 以这种方式建立函数,会致使不一样的做用域链和标识符解析;

二 原型

// 咱们建立的每一个函数都有一个prototype(原型)属性,这个属性是一个对象;函数

// 用途:包含能够由特定类型的全部实例共享的属性和方法;测试

// 理解:prototype是经过调用构造函数建立的那个对象的原型对象;this

// 使用原型的好处是可让全部对象实例共享它所包含的属性和方法;spa

// 也就是说,没必要在构造函数中定义对象信息(属性/方法),而是能够直接将这些信息添加到原型中;prototype

1.原型模式(prototype添加属性和方法)

 1 1.原型模式
 2     function Box(){}                                // 声明构造函数;
 3     Box.prototype.name = 'Lee';                     // 在原型里添加属性和方法;
 4     Box.prototype.age = 100;
 5     Box.prototype.run = function() {
 6         return this.name+this.age+'运行中...';
 7     };
 8     var box1 = new Box();
 9     var box2 = new Box();
10     console.log(box1.run==box2.run);                // =>true;方法引用的地址保持一致;
11 // 在原型中多了两个属性,这两个原型属性都是建立对象时自动生成的;
12 // 1.__proto__:构造函数指向原型对象的一个指针;它的做用:指向构造函数的原型的属性constructor;

13
14// IE浏览器在脚本访问__proto__会不能识别; 
15 
16 // 判断一个实例对象是否指向了该构造函数的原型对象,可使用isPrototypeOf()方法来测试;
17     console.log(Box.prototype.isPrototypeOf(box));    // =>true; 只要实例化对象,即都会指向;
18 
19 // 原型模式的执行流程:
20 // 1.先查找构造函数对象的实例里的属性或方法,如有,马上返回;
21 // 2.若构造函数对象的实例里没有,则去它的原型对象里找,如有,就返回;
22 
23 // 虽然咱们能够经过对象实例访问保存在原型中的值,但却不能访问经过对象实例重写原型中的值;
24     var box1 = new Box();
25     console.log(box1.name);                            // Lee; 原型里的值;
26     bo1.name = 'jack';
27     console.log(box1.name);                            // Jack;实例本身赋的值;
28     var box2 = new Box();
29     console.log(box2.name);                            // Lee;原型里的值;没有被box1修改;
30     // 若是想要box1继续访问原型里的值,能够把构造函数里的属性删除便可;
31     delete box1.name;                                  // 删除实例本身的属性;
32     console.log(box1.name);                            // Lee; 原型里原来的值;

2.原型与in操做符

1 // 如何判断属性是在构造函数的实例里,仍是在原型里? 能够用hasOwnProperty()函数来验证;
2     console.log(box.hasOwnProperty('name'));            // 实例里如有返回true,不然返回false;
3 
4 // in操做符会在经过对象可以访问给定属性时返回true,不管该属性存在与实例中仍是原型中;
5     console.log('name' in box);                         // =>true,存在实例中或原型中;

3.更简单的原型语法(原型+字面量模式)

 1 3.更简单的原型语法(原型+字面量模式)
 2     function Box(){};
 3     Box.prototype = {                                 // 以字面量形式建立包含属性和方法的新对象;
 4         name:'Lee',
 5         age:100,
 6         run:function(){
 7             return this.name+this.age+'运行中...';
 8         }
 9     };
10 
11 // 使用构造函数建立原型对象和使用字面量建立原型对象在使用上基本相同;
12 // 可是,使用字面量建立的原型对象使用constructor属性不会指向实例,而是指向原型对象Object;构造函数的方式则相反;
13     var box = new Box();
14     console.log(box instanceof Box);
15     console.log(box instanceof Object);    
16     console.log(box.constructor == Box);            // 字面量方式,返回false;
17     console.log(box.constructor == Object);         // 字面量方式,返回true;
18     // 若是想让字面量方式的constructor指向实例对象:
19     Box.prototype = {
20         constructor:Box,                            // 直接强制指向便可;
21     }
22 
23     // PS:字面量方式为何constructor会指向Object?
24     // 由于Box.prototype={}这种字面量写法就是建立一个新对象;
25     // 而每建立一个函数,就会同时建立它的prototype,这个对象也会自动获取constructor属性;
26     // 因此,新对象的constructor重写了Box原来的constructor,所以指向了新对象,
27     // 那个新对象没有指定构造函数,那么就默认为是Object;

4.原型的动态性(重写会覆盖以前的内容)

 1 // 原型的声明是有前后顺序的,因此,重写的原型会切断以前的原型;
 2     function Box(){};
 3     Box.prototype = {
 4         constructor:Box,
 5         name:'Lee',
 6         age:100,
 7         run:function(){
 8             return this.age+'运行中...';
 9         }
10     };
11     Box.prototype = {                                // 原型重写了,覆盖了以前的原型;
12         age:200,
13         run:function(){
14             return this.age+'运行中...';
15         }
16     }
17     var box = new Box();
18     console.log(box.run());                            // =>200运行中...;
19     // 重写原型对象切断了现有原型与任何以前已经存在的对象实例之间的联系;对象实例引用的仍然是最初的原型;

5.原生对象的原型

1 // 原型对象不只仅能够在自定义对象的状况下使用,而是ECMAScript内置的引用类型均可以使用这种方式,
2 // 而且内置的引用类型自己也是用了原型;
3     console.log(Array.prototype.sort);                // =>function sort() { [native code] };
4     console.log(String.prototype.substring);          // =>function substring() { [native code] };

6.原型对象的问题

 1 // 原型模式建立对象缺点:省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的;
 2 // 而原型最大的有点就是共享,属性共享;
 3 // 可是,若是原型中的属性包含引用类型(对象),共享就会存在必定问题;
 4     function Box(){};
 5     Box.prototype = {
 6         constructor:Box,
 7         name:'Lee',
 8         age:100,
 9         family:['father','mother'],
10         run:function(){
11             return this.name+this.age+this.family;
12         }
13     };
14     var box1 = new Box();
15     box1.family.push('sister');                     // 为box1的family属性添加了sister;而这个属性被共享到原型了;
16     console.log(box1.run());                        // =>Lee100father,mother,sister;
17     var box2 = new Box();
18     console.log(box2.run());                        // =>Lee100father,mother,sister;
19     // 数据共享致使实例化出的数据不能保存本身的特性;

7.组合使用构造函数模式(对象不共享的数据)和原型模式(对象共享的数据)

 1 // 为了解决构造传参和共享问题,组合构造函数+原型模式:
 2     function Box(name,age){                         // 不共享的使用构造函数;
 3         this.name = name;
 4         this.age = age;
 5         this.family = ['father','moter'];
 6     };
 7     Box.prototype = {                                // 共享的使用原型模式;
 8         constructor:Box,
 9         run:function(){
10             return this.name+this.age+this.family;
11         }
12     };
13     // PS:这种混合模式很好的解决了传参和引用共享的大难题;是建立对象比较好的方法;

8.动态原型模式(将原型封装到构造函数里)

 1 // 原型模式,不论是否调用了原型中的共享方法,它都会初始化原型中的方法;
 2 // 而且在声明一个对象时,构造函数+原型让人感受怪异;最好把构造函数和原型封装到一块儿;
 3     function Box(name,age){                            // 将全部信息封装到构造函数体内;
 4         this.name = name;
 5         this.age = age; 
 6         // 当第一次调用构造函数时,run()方法不存在,而后执行初始化原型;
 7         // 当第二次调用,就不会初始化,而且第二次建立新对象,原型也不会载初始化;
 8         // 这样既获得了封装,又实现了原型方法共享,而且属性都保持独立;
 9         if(typeof this.run != 'function'){            // 仅在第一次调用时初始化;
10             Box.prototype.run = function (){
11                 return this.name+this.age+'运行中...';
12             };
13         }
14     };
15     var box = new Box('lee',10);
16     console.log(box.run());
17 // PS:使用动态原型模式,要注意一点,不能够再使用字面量的方式重写原型,由于会切断实例和新原型之间的联系;

9.寄生构造函数

 1 // 寄生构造函数,其实就是工厂模式+构造模式;这种模式比较通用,但不能肯定对象关系;
 2     function Box(name,age){
 3         var obj = new Object();
 4         obj.name = name;
 5         obj.age = age;
 6         obj.run = function (){
 7             return this.name+this.age+'运行中...';
 8         };
 9         return obj;
10     }

三 继承

1.原型链

 1 // 继承是面向对象中一个比较核心的概念;
 2 // 其余正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承;
 3 // 而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成;
 4 // 实质:利用原型让一个引用类型继承另外一个引用类型的属性和方法;
 5     // 原型继承链:Box ==>> Desk ==>> Table;
 6     function Box(){                                 // Box构造;
 7         this.name = 'Lee';
 8     }
 9     function Desk(){                                // Desk构造;
10         this.age = 100;
11     }
12     Desk.prototype = new Box();                     // 经过建立Box实例,并赋值给Desk.prototype实现的;经过原型,造成链条;    
13                                                     // 实质是:重写了Desk的原型对象,取而代之的是一个新类型Box的实例;
14                                                     // 也就是说原来存在于Box实例中的属性和方法,如今也存在与Desk.prototype中了;
15     var desk = new Desk();
16     console.log(desk.age);                          // 100;
17     console.log(desk.name);                         // =>Lee;
18 
19     function Table(){
20         this.level = 'AAA';
21     }
22     Table.prototype = new Desk();                   // 继续原型链继承;Table继承了Desk;
23     var table = new Table();
24     console.log(table.name);                        // Lee;


2.原型与实例的关系;

 1 // PS:以上原型链继承缺乏一环,那就是Object,全部的构造函数都继承自Object;
 2 // 而继承Object是自动完成的,并不须要手动继承;
 3     console.log(table instanceof Object);              // =>true;
 4     console.log(desk instanceof Table);                // =>false;Desk是Table的超类;
 5     console.log(table instanceof Desk);                // =>true;
 6     console.log(table instanceof Box);                 // =>true;
 7 // 在JS中,被继承的函数称为超类型(父类,基类);
 8 // 继承的函数称为子类型(子类,派生类);
 9 // 继承问题:
10 // 字面量重写原型会中断关系;
11 // 子类型没法给超类型传递参数;

3.借用构造函数(对象冒充)

// 为了解决引用共享和给超类型没法传参问题;指针

 1 // 在子类型构造函数的内部调用超类型构造函数;
 2     function Box(age){
 3         this.name = ['Lee','Jack','Hello'];
 4         this.age = age;
 5     }
 6     function Desk(age){
 7         // 继承了Box;同时还传递了参数;
 8         // 这样一来,就会在新Desk对象上执行Box()函数中定义的全部对象初始化代码;
 9         Box.call(this,age);                            // 对象冒充,Desk继承Box,并能够给超类型传参;
10         // 为了确保Box构造函数不会重写子类型的属性,能够在超类型构造函数后,再添加应该在子类型中定义的属性;
11         this.height = 175;
12 
13     }
14     var desk = new Desk(200);               // 向Desk()函数传参,再经过函数冒用向Box()函数传参;
15     console.log(desk.age);                             // =>200;
16     console.log(desk.name);                            // =>['Lee','Jack','Hello'];
17     desk.name.push('AAA');                             // =>添加的新数据,只添加给desk;
18     console.log(desk.name);                            // =>['Lee','Jack','Hello','AAA'];

4.组合继承(原型链+借用构造函数)

// 借用构造函数虽然解决了引用共享和给超类型没法传参问题,可是没有使用原型,复用则无从谈起;因此须要组合继承模式;code

 1 // 使用原型链实现对原型属性和方法的继承;
 2 // 经过借用构造函数来实现对实例属性的继承;
 3 // 这样,既经过在原型上定义方法实现了函数复用,又能保证每一个实例都有他本身的属性;
 4     function Box(age){                     // 构造函数;
 5         this.name = ['Lee','Jack','Hello'];
 6         this.age = age;
 7     }
 8     Box.prototype.run = function(){             // 原型;
 9         return this.name+this.age;
10     }
11     function Desk(age){
12         Box.call(this,age);                            // 继承属性; 对象冒充; 将Box对象的做用域扩充到Desk中,Desk就会继承Box里的属性和方法;
13     }                              
14     Desk.prototype = new Box();                        // 继承方法; 原型链继承;
15     var desk = new Desk(100);
16     console.log(desk.run());                           // =>Lee,Jack,Hello100
17 // 最经常使用的继承模式; 

5.原型式继承?

 1 // 这种继承借助原型并基于已有的对象建立对象,同时还没必要所以建立自定义类型;
 2     function obj(o){                                // 传递一个字面量函数;
 3         function F(){};                             // 建立一个构造函数;
 4         F.prototype = o;                            // 把字面量函数赋值给构造函数的原型;
 5         return new F();                             // 返回实例化的构造函数;
 6     }
 7     var box = {                                     // 字面量对象;
 8         name:'Lee',
 9         arr:['brother','sisiter']
10     };
11     var box1 = obj(box);
12     console.log(box1.name);                         // =>Lee;
13     box1.name = 'Jack';
14     console.log(box1.name);                         // =>Jack;
15 
16     console.log(box1.arr);                          // =>brother,sister;
17     box1.arr.push('father');                        // 
18     console.log(box1.arr);                          // =>brother,sister,father;
19 
20     var box2 = obj(box);
21     console.log(box2.name);                         // =>Lee;
22     console.log(box2.arr);                          // =>brother,sister,father;引用类型共享了;

6.寄生式继承?

1 // 把原型式+工厂模式结合而来,目的是为了封装建立对象的过程;
2 // 建立一个仅用于封装继承过程的函数,
3     function create(o){                             // 封装建立过程;
4         var f = obj(o);
5         f.run = function(){
6             return this.arr;                        // 一样会共享引用;
7         };
8         return f;
9     }

7.寄生组合式继承?

 1 // 以前说过,组合式继承是JS最经常使用的继承模式;
 2 // 可是,组合式继承也有问题:
 3 // 超类型在使用过程当中会被调用两次:一次是建立子类型的时候,另外一次是在子类型构造函数的内部;
 4     function Box(name){
 5         this.name = name;
 6         this.arr = ['brother','sister'];
 7     }
 8     Box.prototype.run = function(){
 9         return this.name;
10     }
11     function Desk(name,age){
12         Box.call(this,name);                        // 第二次调用Box;
13         this.age = age;
14     }
15     Desk.prototype = new Box();                     // 第一次调用Box;
16 
17 // 寄生组合式继承:
18 // 经过借用构造函数来继承属性,
19 // 经过原型链的混成形式来继承方法;
20 // 解决了两次调用的问题;
21     function obj(o){
22         function F(){};
23         F.prototype = o;
24         return new F();
25     }
26     function create(box,desk){
27         var f = obj(box.prototype);
28         f.constructor = desk;
29         desk.prototype = f;
30     }
31     function Box(name){
32         this.name = name;
33         this.arr = ['brother','sister'];
34     }
35     Box.prototype.run = function(){
36         return this.name;
37     }
38     function Desk(name,age){
39         Box.call(this,name);
40         this.age = age;
41     }
42     inheritPrototype(Box,Desk);                        // 经过这里实现继承;
43 
44     var desk = new Desk('Lee',100);
45     desk.arr.push('father');
46     console.log(desk.arr);
47     console.log(desk.run());
48 
49     var desk2 = new Desk('Jack',200);
50     console.log(desk2.arr);                            // 两次引用问题解决;

四 小结

1.建立对象

1 // 对象能够在代码执行过程当中建立和加强,所以具备动态性而非严格定义的实体;
2 // 在没有类的状况下,能够采用下列模式建立对象;
3 // (1).工厂模式:使用简单的函数建立对象,为对象添加属性和方法,而后返回对象;
4 // 这个模式后来被构造函数模式所取代;
5 // (2).构造函数模式:能够自定义引用类型,能够像建立内置对象实例一眼使用new操做符;
6 // 缺点:它的每一个成员都没法获得复用,包括函数;因为函数能够不局限于任何对象,所以没有理由不在多个对象间共享函数;
7 // (3).原型模式:使用函数的prototype属性来指定那些应该共享的属性和方法;
8 // 组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,使用原型定义共享的属性和方法;

2.原型链

1 // 原型链的构建是经过将一个类型的实例赋值给另外一个构造函数的原型实现的;
2 // 子类型能够访问到超类型的全部属性和方法;
3 // 原型链的问题是对象实例共享全部继承的属性和方法,所以不适宜单独使用;
4 // 解决方案:借用构造函数,即在子类型构造函数的内部调用超类型构造函数;
5 // 这样就能够作到每一个实例都具备本身的属性,同时还能保证只使用构造函数来定义类型;
6 // 使用最多的继承模式是组合继承;它使用原型链继承共享的属性和方法,而经过借用构造函数继承实例属性;

3.继承模式

1 // (1).原型式继承:能够在没必要预先定义构造函数的状况下实现继承;其本质是执行对给定对象的浅复制;
2 // 而复制获得的副本开能够获得进一步改造;
3 // (2).寄生式继承:基于某个对象或某些信息建立一个对象,而后加强对象,最后返回对象;
4 // 为了解决组合继承模式因为屡次调用超类型构造函数而致使的低效率问题,能够将这个模式与组合继承一块儿使用;
5 // (3).寄生组合式继承:集寄生式继承和组合式继承的有点于一身,是实现基于类型继承的最有效方式;
相关文章
相关标签/搜索