美文网首页
【北京分院一百七十一期】JS中的面向对象编程

【北京分院一百七十一期】JS中的面向对象编程

作者: return_3711 | 来源:发表于2017-05-20 20:27 被阅读0次

    1.背景介绍

    什么是面向对象编程?

    “面向对象编程”(Object OrientedProgramming,缩写为OOP)是目前主流的编程范式。它的核心思想是将真实世界中各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

    主要概念为:把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)/泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派(dynamicdispatch)。

    Javascript是一种基于对象(object-based)的语言,遇到的东西几乎都是对象,但是它不是一种面对对象的语言。像其他语言里面的class(类),它就没办法直接用了。(听说ES 6可以用了,笔者一直学的ES5,6暂未研究,有兴趣的同学可以去看看教程)

    2.知识剖析

    2.1对象的概念

    因为JS是一个基于对象的语言,所以我们遇到的大多数东西几乎都是对象。例如函数就是一个对象,如果你要在js里面新建一个对象,这样写其实就是创建了一个object的实例。对象就是一个容器,封装了属性和方法。属性就是对象的状态,比如下面的name属性。方法就是写在对象里面的函数,也就是对象的行为,比如下面的sayName方法。

    var person = new object();

    person.name = "Tom";

    person.sayNmae = function() {

    alert(this.name);

    }

    2.2 工厂模式
    “面向对象编程”的第一步,就是要生成“对象”。但是很多时候我们不得不面临重复生成很多对象的情况,如果我有一千个人要记录他们的信息,像上面这种方法写的话,大大增加了代码的重复量,为了解决这个问题,人们开始使用工厂模式的一种变体,写法如下页。虽然工厂模式解决了代码复用的问题,但是却没办法显示实例(person1)和对象o之间的关系,比如aler(person1 instanceof o);

    代码演示:
    function Person(name,age, job) {

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = function() {

    alert(this.name)

    };

    }

    person1 = new Person("Tom",20,"Engineer");

    person2 = new Person("Damon",22,"Waiter");

    2.2 构造函数

    后来就出现了构造函数,用来创建特定类型的对象,可以将实例和对象联系起来,用到了JS中的“this”,写法如下:

    这样对象和实例之间就有关系了,以new这种方式调用构造函数会经历4个步骤:

    (1)创建一个新对象。

    (2)将构造函数的作用域赋给新对象(这个this就指向了这个新对象)。

    (3)执行函数内代码(给对象添加属性)

    (4)返回新对象。

    代码演示:

    function Person(name,age, job) {

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = function() {

    alert(this.name)

    };

    }

    person1 = new Person("Tom",20,"Engineer");

    person2 = new Person("Damon",22,"Waiter");

    构造函数特点:

    上面代码中,Persoon就是构造函数,它提供模板,用来生成对象实例。为了与普通函数区别,构造函数名字的第一个字母通常大写。

    构造函数的两个特点:

    1.函数体内部使用了this关键字,代表了所要生成的对象实例。

    2.生成对象的时候,必需用new命令,调用函数。

    如果忘了使用new命令,直接调用构造函数会导致构造函数变成普通函数,就不会生成实例对象,并且此时的this这时代表全局对象,将造成一些意想不到的结果。

    var Vehicle = function (){

    this.price = 1000;

    };

    var v = Vehicle();

    v.price

    // Uncaught TypeError: Cannot read property 'price' of undefined

    上面代码中,调用Vehicle构造函数时,忘了加上new命令。结果,price属性变成了全局变量,而变量v变成了undefined。

    因此必须小心,记得使用new命令。

    2.3原型和原型链

    原型prototype

    JavaScript的每个对象都继承另一个对象,后者称为“原型” (prototype)对象。只有null除外,它没有自己的原型对象。

    原型对象上的所有属性和方法,都能被派生对象共享。这就是JavaScript继承机制的基本设计。

    通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。

    原型链

    对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype

    chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。

    “原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。

    需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

    利用原型(prototype)的继承特性,我们可以将我们的函数写成

    function Person() {

    };

    Person.prototype.name = "Tom";

    Person.prototype.age = "20";

    Person.prototype.job = "engineer";

    Person.prototype.sayName = function() {

    alert(this.name);

    };

    var person1 = new Person();

    var person2 = new Person();

    alert(person1.sayName == person2.sayName); //true

    因为原型的继承,person1和person2的prototype都指向Person的prototype,所以这两个函数其实是相等的。但是用工厂函数或者构造模式, alert(person1.sayName == person2.sayName);就绝对不会为真了。

    奇淫巧技1:每次写属性都要加一个prototype是不是很麻烦,其实还有另外一种写法

    function Person() {

    }

    Person.prototype = {

    name : "Tom";

    age  : "20";

    job : "engineer";

    sayName : function() {

    alert(this.name);

    }

    }

    var person1 = new Person();

    var person2 = new Person();

    alert(person1.sayName == person2.sayName); //true

    2.4 构造函数的继承

    让一个构造函数继承另一个构造函数,是非常常见的需求。

    也有多种方法实现,各有优缺点。比如现在有一个动物对象的构造函数,和一个猫对象的构造函数。

    function Animal() {

    this.species = “动物”;

    };

    function Cat(name,color) {

    this.name = name;

    this.color = color;

    }

    如何才能使Cat继承Animal呢?

    2.4.1 构造函数绑定

    第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

    function Cat(name,color){

    Animal.apply(this, arguments); //加的

    this.name = name;

    this.color = color;

    }

    var cat1 = new Cat("大毛","黄色");

    alert(cat1.species); // 动物

    2.4.2 prototype(原型)模式

    第二种方法更常见,使用prototype属性。

    如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

    Cat.prototype = new Animal();

    Cat.prototype.constructor = Cat;

    var cat1 = new Cat("大毛","黄色");

    alert(cat1.species); // 动物

    代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。相当于将Cat原先的原型对象删除,重新赋一个Animal实例的值。但是任何一个prototype对象都有一个constructor属性,指向它的构造函数。这个时候Cat的构造函数也改变了,变成了Animal。

    2.4.2 prototype(原型)模式

    所以我们需要“Cat.prototype.constructor = Cat”将Cat的构造函数重新指向为Cat,不然的话会很容易出问题。

    这是很重要的一点,编程时务必要遵守。如果替换了prototype对象,

    b.prototype = new a();

    那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。b.prototype.constructor = b;

    2.4.3 直接继承prototype(原型)

    第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。现在我们将Animal对象改写

    function Animal() {

    Animal.prototype.species = "动物";

    }

    然后,将Cat的prototype对象,指向Animal的prototype对象,这样就完成了继承。

    Cat.prototype = Animal.prototype;

    Cat.prototype.constructor = Cat;

    var cat1 = new Cat("大毛","黄色");

    alert(cat1.species); // 动物

    2.4.3 直接继承prototype(原型)

    与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。所以Animal.prototype的构造函数也变成了Cat。

    这个时候我们就需要引入一个空对象作为中转的中介,无论Cat的constructor如何变,只会影响到中转对象F而无法影响到父对象Animal了。

    var F = function(){};

    F.prototype = Animal.prototype;

    Cat.prototype = new F();

    Cat.prototype.constructor = Cat;

    2.4.3 直接继承prototype(原型)

    然后我们将上述方法封装成为一个函数,使用起来就很方便了

    function extend(Child, Parent) {

    var F = function(){};

    F.prototype = Parent.prototype;

    Child.prototype = new F();

    Child.prototype.constructor = Child;

    Child.uber = Parent.prototype;

    }

    2.4.3 直接继承prototype(原型)

    使用的时候方法如下:

    extend(Cat,Animal);

    var cat1 = new Cat("大毛","黄色");

    alert(cat1.species); // 动物

    奇淫巧技2:封装函数的时候怎么方便怎么写不必太过考虑语义化的东西,比如写个状态机,直接将状态用数字表示,这样比字符串的形式好判断多了。但是一点也不语义化。

    3.常见问题

    必须要声明new来创建实例对象吗?

    4.解决方案

    1.必须要声明new来创建实例对象吗?

    为了保证构造函数必须与new命令一起使用,一个解决办法是,在构造函数内部使用严格模式,即第一行加上use strict。

    function Fubar(foo, bar){

    'use strict';

    this._foo = foo;

    this._bar = bar;

    }

    Fubar();

    // TypeError: Cannot set property '_foo' of undefined

    上面代码的Fubar为构造函数,use

    strict命令保证了该函数在严格模式下运行。由于在严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错(JavaScript不允许对undefined添加属性)。

    另一个解决办法,是在构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

    function Fubar(foo, bar){

    if (!(this instanceof Fubar)) {

    return new Fubar(foo, bar);

    }

    this._foo = foo;

    this._bar = bar;

    }

    Fubar(1, 2)._foo // 1

    (new Fubar(1, 2))._foo // 1

    上面代码中的构造函数,不管加不加new命令,都会得到同样的结果。

    >5.编码实战

    用面对对象编程的思想写状态机

    6.扩展思考

    面向对象与面向过程的区别?

    传统的过程式编程(procedural programming)由一系列函数或一系列指令组成;而面向对象编程的程序由一系列对象组成。

    每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。因此,面向对象编程具有灵活性、代码的可重用性、模块性等特点,容易维护和开发,非常适合多人合作的大型应用型软件项目。

    7.参考文献

    参考一:http://javascript.ruanyifeng.com/oop/basic.html">阮一峰

    参考二:

    href="http://www.ruanyifeng.com/blog/search.html?cx=016304377626642577906%3Ab_e9skaywzq&cof=FORID%3A11&ie=UTF-8&q=Javascript+%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B&sa.x=9&sa.y=8">阮一峰

    参考三:《Javascript高级程序设计》chapter 6

    8.更多讨论

    new命令的原理?

    构造函数中的return语句的作用?

    面向对象编程的继承原理?

    鸣谢

    感谢大家观看

    PTT链接


    JS中的面向对象编程_腾讯视频

    ------------------------------------------------------------------------------------------------------------------------

    技能树.IT修真院

    “我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。

    这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。快来与我一起学习吧 !http://www.jnshu.com/login/1/96194340

    相关文章

      网友评论

          本文标题:【北京分院一百七十一期】JS中的面向对象编程

          本文链接:https://www.haomeiwen.com/subject/xgstxxtx.html