JavaScript 面向对象与原型_javascript技巧

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

一 创建对象
1.普通的创建对象

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

2. 工厂模式创建对象

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

3.构造函数创建对象

// ECMAScript采用构造函数(构造方法)可用来创建特定的对象;
  function Box(name,age){          // 构造函数模式;
    this.name = name;           // this代表对象Box;
    this.age = age;
    this.run = function(){
      return this.name+this.age+"运行中...";
    };
  }
  var box1 = new Box("lee",100);     // 要创建对象的实例必须用new操作符;
  var box2 = new Box("jack",200);    // box1和box2都是Box对象的实例;
  console.log(box1 instanceof Box);   // true;很清晰的识别box1从属于Box;
// 使用构造函数,即解决了重复实例化的问题,有解决了对象识别的问题;

 使用构造函数与工厂模式不同之处:
(1).构造函数方法没有显示的创建对象(new Object);
(2).直接将属性和方法赋值给this对象;
(3).没有return语句;1 // 构造函数规范:
(1).函数名(function Box)和实例化构造名(new Box)相同且大写;
(2).通过构造函数创建实例对象,必须使用new运算符;

// 构造函数和普通函数的区别:
  var box = new Box('lee',100);        // 构造模式调用;
  Box('lee',200);               // 普通模式调用,无效;

  var o = new Object();
  Box.call(o,'jack',200);           // 对象冒充调用;
  // 将Box对象作用域扩充到对象o;Box()方法的运行环境已经变成了对象o里;

 构造函数的问题:
使用构造函数创建每个实例的时候,构造函数里的方法都要在每个实例上重新创建一遍;
因为ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象;
以这种方式创建函数,会导致不同的作用域链和标识符解析;

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

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

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

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

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

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

1.原型模式
  function Box(){}                // 声明构造函数;
  Box.prototype.name = 'Lee';           // 在原型里添加属性和方法;
  Box.prototype.age = 100;
  Box.prototype.run = function() {
    return this.name+this.age+'运行中...';
  };
  var box1 = new Box();
  var box2 = new Box();
  console.log(box1.run==box2.run);        // =>true;方法引用的地址保持一致;
// 在原型中多了两个属性,这两个原型属性都是创建对象时自动生成的;
// 1.__proto__:构造函数指向原型对象的一个指针;它的作用:指向构造函数的原型的属性constructor;
 14// IE浏览器在脚本访问__proto__会不能识别; 15
// 判断一个实例对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试;
  console.log(Box.prototype.isPrototypeOf(box));  // =>true; 只要实例化对象,即都会指向;

// 原型模式的执行流程:
// 1.先查找构造函数对象的实例里的属性或方法,若有,立刻返回;
// 2.若构造函数对象的实例里没有,则去它的原型对象里找,若有,就返回;

// 虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值;
  var box1 = new Box();
  console.log(box1.name);              // Lee; 原型里的值;
  bo1.name = 'jack';
  console.log(box1.name);              // Jack;实例自己赋的值;
  var box2 = new Box();
  console.log(box2.name);              // Lee;原型里的值;没有被box1修改;
  // 如果想要box1继续访问原型里的值,可以把构造函数里的属性删除即可;
  delete box1.name;                 // 删除实例自己的属性;
  console.log(box1.name);              // Lee; 原型里原来的值;

2.原型与in操作符

如何判断属性是在构造函数的实例里,还是在原型里? 可以用hasOwnProperty()函数来验证;
console.log(box.hasOwnProperty('name')); // 实例里若有返回true,否则返回false;
in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在与实例中还是原型中;
console.log('name' in box); // =>true,存在实例中或原型中;3.更简单的原型语法(原型+字面量模式)

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

  function Box(){};
  Box.prototype = {                 // 以字面量形式创建包含属性和方法的新对象;
    name:'Lee',
    age:100,
    run:function(){
      return this.name+this.age+'运行中...';
    }
  };

// 使用构造函数创建原型对象和使用字面量创建原型对象在使用上基本相同;
// 但是,使用字面量创建的原型对象使用constructor属性不会指向实例,而是指向原型对象Object;构造函数的方式则相反;
  var box = new Box();
  console.log(box instanceof Box);
  console.log(box instanceof Object);
  console.log(box.constructor == Box);      // 字面量方式,返回false;
  console.log(box.constructor == Object);     // 字面量方式,返回true;
  // 如果想让字面量方式的constructor指向实例对象:
  Box.prototype = {
    constructor:Box,              // 直接强制指向即可;
  }

  // PS:字面量方式为什么constructor会指向Object?
  // 因为Box.prototype={}这种字面量写法就是创建一个新对象;
  // 而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性;
  // 所以,新对象的constructor重写了Box原来的constructor,因此指向了新对象,
  // 那个新对象没有指定构造函数,那么就默认为是Object;

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

// 原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型;
  function Box(){};
  Box.prototype = {
    constructor:Box,
    name:'Lee',
    age:100,
    run:function(){
      return this.age+'运行中...';
    }
  };
  Box.prototype = {                // 原型重写了,覆盖了之前的原型;
    age:200,
    run:function(){
      return this.age+'运行中...';
    }
  }
  var box = new Box();
  console.log(box.run());              // =>200运行中...;
  // 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;对象实例引用的仍然是最初的原型;

5.原生对象的原型

// 原型对象不仅仅可以在自定义对象的情况下使用,而是ECMAScript内置的引用类型都可以使用这种方式,
// 并且内置的引用类型本身也是用了原型;
console.log(Array.prototype.sort); // =>function sort() { [native code] };
console.log(String.prototype.substring); // =>function substring() { [native code] };

6.原型对象的问题

// 原型模式创建对象缺点:省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的;
// 而原型最大的有点就是共享,属性共享;
// 但是,如果原型中的属性包含引用类型(对象),共享就会存在一定问题;
  function Box(){};
  Box.prototype = {
    constructor:Box,
    name:'Lee',
    age:100,
    family:['father','mother'],
    run:function(){
      return this.name+this.age+this.family;
    }
  };
  var box1 = new Box();
  box1.family.push('sister');           // 为box1的family属性添加了sister;而这个属性被共享到原型了;
  console.log(box1.run());            // =>Lee100father,mother,sister;
  var box2 = new Box();
  console.log(box2.run());            // =>Lee100father,mother,sister;
  // 数据共享导致实例化出的数据不能保存自己的特性;

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

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

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

// 原型模式,不管是否调用了原型中的共享方法,它都会初始化原型中的方法;
// 并且在声明一个对象时,构造函数+原型让人感觉怪异;最好把构造函数和原型封装到一起;
  function Box(name,age){              // 将所有信息封装到构造函数体内;
    this.name = name;
    this.age = age;
    // 当第一次调用构造函数时,run()方法不存在,然后执行初始化原型;
    // 当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会载初始化;
    // 这样既得到了封装,又实现了原型方法共享,并且属性都保持独立;
    if(typeof this.run != 'function'){      // 仅在第一次调用时初始化;
      Box.prototype.run = function (){
        return this.name+this.age+'运行中...';
      };
    }
  };
  var box = new Box('lee',10);
  console.log(box.run());
// PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系;

9.寄生构造函数

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

三 继承
1.原型链

// 继承是面向对象中一个比较核心的概念;
// 其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承;
// 而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成;
// 实质:利用原型让一个引用类型继承另一个引用类型的属性和方法;
  // 原型继承链:Box ==>> Desk ==>> Table;
  function Box(){                 // Box构造;
    this.name = 'Lee';
  }
  function Desk(){                // Desk构造;
    this.age = 100;
  }
  Desk.prototype = new Box();           // 通过创建Box实例,并赋值给Desk.prototype实现的;通过原型,形成链条;
                          // 实质是:重写了Desk的原型对象,取而代之的是一个新类型Box的实例;
                          // 也就是说原来存在于Box实例中的属性和方法,现在也存在与Desk.prototype中了;
  var desk = new Desk();
  console.log(desk.age);             // 100;
  console.log(desk.name);             // =>Lee;

  function Table(){
    this.level = 'AAA';
  }
  Table.prototype = new Desk();          // 继续原型链继承;Table继承了Desk;
  var table = new Table();
  console.log(table.name);            // Lee;

2.原型与实例的关系;

// PS:以上原型链继承缺少一环,那就是Object,所有的构造函数都继承自Object;
// 而继承Object是自动完成的,并不需要手动继承;
  console.log(table instanceof Object);       // =>true;
  console.log(desk instanceof Table);        // =>false;Desk是Table的超类;
  console.log(table instanceof Desk);        // =>true;
  console.log(table instanceof Box);         // =>true;
// 在JS中,被继承的函数称为超类型(父类,基类);
// 继承的函数称为子类型(子类,派生类);
// 继承问题:
// 字面量重写原型会中断关系;
// 子类型无法给超类型传递参数;

3.借用构造函数(对象冒充)
// 为了解决引用共享和给超类型无法传参问题;

// 在子类型构造函数的内部调用超类型构造函数;
  function Box(age){
    this.name = ['Lee','Jack','Hello'];
    this.age = age;
  }
  function Desk(age){
    // 继承了Box;同时还传递了参数;
    // 这样一来,就会在新Desk对象上执行Box()函数中定义的所有对象初始化代码;
    Box.call(this,age);              // 对象冒充,Desk继承Box,并可以给超类型传参;
    // 为了确保Box构造函数不会重写子类型的属性,可以在超类型构造函数后,再添加应该在子类型中定义的属性;
    this.height = 175;

  }
  var desk = new Desk(200);               // 向Desk()函数传参,再通过函数冒用向Box()函数传参;
  console.log(desk.age);               // =>200;
  console.log(desk.name);              // =>['Lee','Jack','Hello'];
  desk.name.push('AAA');               // =>添加的新数据,只添加给desk;
  console.log(desk.name);              // =>['Lee','Jack','Hello','AAA'];

4.组合继承(原型链+借用构造函数)
// 借用构造函数虽然解决了引用共享和给超类型无法传参问题,但是没有使用原型,复用则无从谈起;所以需要组合继承模式;

// 使用原型链实现对原型属性和方法的继承;
// 通过借用构造函数来实现对实例属性的继承;
// 这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有他自己的属性;
  function Box(age){                    // 构造函数;
    this.name = ['Lee','Jack','Hello'];
    this.age = age;
  }
  Box.prototype.run = function(){            // 原型;
    return this.name+this.age;
  }
  function Desk(age){
    Box.call(this,age);              // 继承属性; 对象冒充; 将Box对象的作用域扩充到Desk中,Desk就会继承Box里的属性和方法;
  }                             
  Desk.prototype = new Box();            // 继承方法; 原型链继承;
  var desk = new Desk(100);
  console.log(desk.run());              // =>Lee,Jack,Hello100
// 最常用的继承模式;

5.原型式继承?

// 这种继承借助原型并基于已有的对象创建对象,同时还不必因此创建自定义类型;
  function obj(o){                // 传递一个字面量函数;
    function F(){};               // 创建一个构造函数;
    F.prototype = o;              // 把字面量函数赋值给构造函数的原型;
    return new F();               // 返回实例化的构造函数;
  }
  var box = {                   // 字面量对象;
    name:'Lee',
    arr:['brother','sisiter']
  };
  var box1 = obj(box);
  console.log(box1.name);             // =>Lee;
  box1.name = 'Jack';
  console.log(box1.name);             // =>Jack;

  console.log(box1.arr);             // =>brother,sister;
  box1.arr.push('father');            //
  console.log(box1.arr);             // =>brother,sister,father;

  var box2 = obj(box);
  console.log(box2.name);             // =>Lee;
  console.log(box2.arr);             // =>brother,sister,father;引用类型共享了;

6.寄生式继承?

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

7.寄生组合式继承?

// 之前说过,组合式继承是JS最常用的继承模式;
// 但是,组合式继承也有问题:
// 超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部;
  function Box(name){
    this.name = name;
    this.arr = ['brother','sister'];
  }
  Box.prototype.run = function(){
    return this.name;
  }
  function Desk(name,age){
    Box.call(this,name);            // 第二次调用Box;
    this.age = age;
  }
  Desk.prototype = new Box();           // 第一次调用Box;

// 寄生组合式继承:
// 通过借用构造函数来继承属性,
// 通过原型链的混成形式来继承方法;
// 解决了两次调用的问题;
  function obj(o){
    function F(){};
    F.prototype = o;
    return new F();
  }
  function create(box,desk){
    var f = obj(box.prototype);
    f.constructor = desk;
    desk.prototype = f;
  }
  function Box(name){
    this.name = name;
    this.arr = ['brother','sister'];
  }
  Box.prototype.run = function(){
    return this.name;
  }
  function Desk(name,age){
    Box.call(this,name);
    this.age = age;
  }
  inheritPrototype(Box,Desk);            // 通过这里实现继承;

  var desk = new Desk('Lee',100);
  desk.arr.push('father');
  console.log(desk.arr);
  console.log(desk.run());

  var desk2 = new Desk('Jack',200);
  console.log(desk2.arr);              // 两次引用问题解决;

四 小结


1.创建对象

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

2.原型链

原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的;
子类型可以访问到超类型的所有属性和方法;
原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用;
解决方案:借用构造函数,即在子类型构造函数的内部调用超类型构造函数;
这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数来定义类型;
使用最多的继承模式是组合继承;它使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性;

3.继承模式

(1).原型式继承:可以在不必预先定义构造函数的情况下实现继承;其本质是执行对给定对象的浅复制;
而复制得到的副本开可以得到进一步改造;
(2).寄生式继承:基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象;
为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用;
(3).寄生组合式继承:集寄生式继承和组合式继承的有点于一身,是实现基于类型继承的最有效方式;

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索面向对象
原型
面向对象原型链、js面向对象 原型继承、基于原型的面向对象、js面向对象原型、js面向对象原型链,以便于您获取更多的相关知识。

时间: 2024-09-15 14:26:29

JavaScript 面向对象与原型_javascript技巧的相关文章

JavaScript面向对象程序设计教程_javascript技巧

JavaScript中对象的定义为:无序属性的集合,其属性可以包含基本值.对象或者函数.可以把对象想象成散列表,就是一组名值对(key:value),其中值可以是数据或函数,每个对象都是基于一个引用类型创建的. 理解对象 前面的博客里写过创建对象的方式有两种,一种是创建一个object的实例,另一种是使用对象字面量法: var person = new Object(); person.sex = man; person.name = bluce person.age = 58; person.

深入浅析JavaScript面向对象和原型函数_javascript技巧

对象,是javascript中非常重要的一个梗,是否能透彻的理解它直接关系到你对整个javascript体系的基础理解,说白了,javascript就是一群对象在搅..(哔!). 下面给大家介绍下常用的几种对象创建模式 使用new关键字创建 最基础的对象创建方式,无非就是和其他多数语言一样说的一样:没对象,你new一个呀! var gf = new Object(); gf.name = "tangwei"; gf.bar = "c++"; gf.sayWhat =

JavaScript使用Prototype实现面向对象的方法_javascript技巧

本文实例讲述了JavaScript使用Prototype实现面向对象的方法.分享给大家供大家参考.具体分析如下: prototype 是 Function 对象的一个属性,这个属性指向另一个对象. 这个对象的所有属性和方法,都会被构造函数的实例继承. 同时 prototype 又存在一个指向构造函数的引用 constructor,这样就成功的构成一个循环引用的原型链结构. 我们可以把那些不变的属性和方法,直接定义在 prototype 对象上, 节省内存开销. function Cat(name

浅谈Javascript中的函数、this以及原型_javascript技巧

关于函数 在Javascript中函数实际上就是一个对象,具有引用类型的特征,所以你可以将函数直接传递给变量,这个变量将表示指向函数"对象"的指针,例如: function test(message){ alert(message); } var f = test; f('hello world'); 你也可以直接将函数申明赋值给变量: var f = function(message){ alert(message); }; f('hello world'); 在这种情况下,函数申明

详解JavaScript基于面向对象之继承_javascript技巧

一.面相对象继承机制       这个实例使用UML很好的解释了继承机制.       说明继承机制最简单的方式是,利用一个经典的例子就是几何形状.实际上,几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆的一种,它只有一个焦点.三角形.矩形和五边形都是多边形的一种,具有不同数量的边.正方形是矩形的一种,所有的边等长.这就构成了一种完美的继承关系,很好的解释了面向对象的继承机制.        在这个例子中,形状是椭圆形和多边形的基类(通常我们也可以叫它父类,所有类都由

深入认识JavaScript中的函数_javascript技巧

概述 函数是进行模块化程序设计的基础,编写复杂的Ajax应用程序,必须对函数有更深入的了解.JavaScript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的.通过函数对象的性质,可以很方便的将一个函数赋值给一个变量或者将函数作为参数传递.在继续讲述之前,先看一下函数的使用语法: function func1(-){-} var func2=function(-){-}; var func3=function func4(-){-}; var func5=new Functio

简单分析javascript面向对象与原型

  为了说明 JavaScript 是一门彻底的面向对象的语言,首先有必要从面向对象的概念着手 , 探讨一下面向对象中的几个概念:1.一切事物皆对象,2.对象具有封装和继承特性,3.对象与对象之间使用消息通信,各自存在信息隐藏 本文主要内容参考来自JavaScript高级程序设计,面向对象与原型章节: 1.工厂模式 ECMAScript 可以通过工厂模式来创建对象: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //工厂模式 function

简单分析javascript面向对象与原型_jquery

本文主要内容参考来自JavaScript高级程序设计,面向对象与原型章节: 1.工厂模式 ECMAScript 可以通过工厂模式来创建对象: //工厂模式 function createObject(name, age) { var obj = new Object(); //创建对象 obj.name = name; //添加属性 obj.age = age; obj.run = function () { //添加方法 return this.name + this.age + '运行中..

js面向对象的写法_javascript技巧

本文归纳了js面向对象的几种常见写法,分享给大家供大家参考,具体内容如下 1.工厂方式 var Circle = function() { var obj = new Object(); obj.PI = 3.14159; obj.area = function( r ) { return this.PI * r * r; } return obj; } var c = new Circle(); alert( c.area( 1.0 ) ); 2.比较正规的写法 function Circle