前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面向对象的JavaScript

面向对象的JavaScript

作者头像
一粒小麦
发布2019-07-18 17:02:34
7450
发布2019-07-18 17:02:34
举报
文章被收录于专栏:一Li小麦

如果它走起路像鸭子,叫起来也是鸭子,那么它就是鸭子。 ——鸭子模型

什么是对象,面向对象(Object-Orented,OO)的抽象

从前在Javascript王国里有一个国王,他觉得世界上最美妙的声音就是鸭子的叫声,于是国王召集大臣,要组建一个1000只鸭子组成的合唱团。大臣们找遍了全国终于找到999只鸭子,但是始终还差一只,最后大臣发现一直特别的鸡,它的叫声跟鸭子一模一样,于是这只鸡就成为了合唱团的最后一员。

这个故事告诉我们,国王要听的只是鸭子的叫声,这个声音的主人到底是一个鸡还是要鸭子并不重要。鸭子类型指导我们只关注对象的行为,而不关注对象本身。

ECMA-262把对象定义为:对象是无序属性的集合。属性可以包括基本值,对象或者函数。

JS中的面向对象、面向对象的特点

由鸭子模型到封装

通俗点就是说:对象是一个对外封闭的整体,不关注内部细节,外界只需要掌握其属性或者说是操作方法就可以了。

比如jquery:大多数时候用好就行了,使用者无需关心怎么实现的。 面向对象不只是局限于编程的思想,而是通用的概念。

多态

主人家里养了两只动物,分别是一只鸭子和一只鸡,当主人向它们发出“叫”的命令时,鸭子会“嘎嘎嘎”叫,而鸡会“咯咯咯”。这两种动物会以自己的方式来发出叫声。它们同样“都是动物”并且可以发出叫声,但根据主人的主指令,它们会发出不同的叫声。

同一操作作用于不同的对象,结果不同。JavaScript的多态实际上是吧做什么和谁去做区分开了。

多态依赖于继承

父类存在时,子类只需要继承了父类的对象,就可以实现一个新的对象。多重继承:继承多个父级的属性。

对象组成——属性和方法

狭义的属性是静态的。而动态的称为方法。

代码语言:javascript
复制
var a=12;//a是一个变量
var Aaa.a=12//a是一个属性——它依附于对象Aaa
function b(){alert(a)};//这是一个函数
Aaa.b=fuction(){alert(a)}//这是对象Aaa一个方法

【小结】属性本质是变量,区别在于从属关系。方法本质是函数。

第一次面向对象

首先定义一个show方法

代码语言:javascript
复制
var arr=[1,3123,43,534];
arr.show=function(){
    alert(this.length);
}
arr.show();

弹出结果是4,这个show方法里的this就是arr。

this实际上表示当前的方法属于谁。 this:谁发生事件,this就是谁。

代码语言:javascript
复制
function show () {
    alert(this);
}

弹出结果是window——说明show函数只是顶层对象window的一个方法。所以上述程序等价于:

代码语言:javascript
复制
window.show=function() {
    alert(this);
}

没有对象?那就创建一个吧

通过Object创建一个自己的对象

代码语言:javascript
复制
var obj=new Object();

obj.name='欲饮琵琶马上催';
obj.sex='女';
obj.show=function(){
    alert('我的博客名是:'+this.name);
}
obj.showSex=function(){
    alert('我是'+this.showSex+'的');
}

这个对象就完成了,不但有名字,还有性别,挺好挺好。 事实上我们需要多个对象时,如果每次都这样定义,这样写就及其麻烦。

工厂模式

解决此问题比较好的思路是工厂模式。通过此模式,可以抽象对象的创建过程。 创建一个函数(createPerson),把上面的代码包装起来。

代码语言:javascript
复制
function createPerson(name,sex){
    var obj=new Object();

    obj.name=name;
    obj.sex=sex;
    obj.showName=function(){
        alert('我的博客名是:'+this.name);
    }
    obj.showSex=function(){
        alert('我是'+this.sex+'的');
    };
    return obj;//必须返回对象本身
}

此函数接收两个参数,一个是名字,一个是性别 需要创建时,可以写

代码语言:javascript
复制
var person1=createPerson('葡萄美酒夜光杯','男');
var person2=createPerson('欲饮琵琶马上催','女');

p1.showName();

那就可以想调用就调用了。

何以谓之工厂?

这样,通过函数,只要创建了对象,把名字,性别等作为参数传递进去,就可以(抽象地)创建一个新对象。 工厂方式特征——原料(obj),流水生产线(构造函数),出厂线(调用)。 p1=new createPerson();然后把构造函数中的obj改为this 工厂模式解决了创建多个相似对象的问题,但是没有解决对象识别问题。

缺点:(本质还是new的问题)
  • 创建对象时代码没有new,因为var obj=new Object()是在函数内部定义了的。对外不起作用。有和没有new是个细微而不可忽视的差别。
  • 每个对象都有一个自己的方法函数。浪费资源。比如说,person1和person2两变量都有showName方法,代码内容完全一样。但是person1.showName()!==person2.showName()。其实就是创建了2个对象,就跟学校公厕一样,没有必要给学校每个人备一个厕所。在工厂模式下,事实上只需要共用一套方法就可以了。

构造函数模式

上面说过createPerson其实是个普通函数,但是它是用来创建对象的函数。所以得名构造函数。 接下来解决工厂模式没有new的问题。把这个构造函数改名为Person以示区别。

代码语言:javascript
复制
function Person(name,sex){
    this.name=name;
    this.sex=sex;
    this.showName=function(){
        alert('我的博客名是:'+this.name);
    }
    this.showSex=function(){
        alert('我是'+this.sex+'的');
    };
}

var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
person2.showName();

效果完全一样。

构造函数的哲学

与createPerson相比,没有了return语句。在this写出来以后,抽象地指向了一个Object()。 比如在javascript中抽象定义"马"这个概念,然后定义“马”应该有名字,有颜色..(属性),还有可以做的事情——比如奔跑…(方法)。而new Horse(…)就是创建了一个Horse的实例,就如同通过告诉告诉计算机“马”是什么东西,然后再告诉计算机“ta是一匹马,白色,名字叫的卢,日行千里”,那计算机很自然根据马的概念解析出“的卢是一匹日行千里的白马”。

每当new一个Person:

  • 就创建了一个对象。
  • 将构造函数的作用域赋值给了新对象(this指向了这个对象)。
  • 属性也赋给了该对象——函数的作用域赋给了此对象。
  • 最后再返回新的对象。

现在可以慢慢理解面向对象编程的三个特点了。"的卢"属于世间万物(window),也属于我们所封装的马类对象,继承了它所有父级的特点,具有万物特点比如具有window下的常用属性。也继承了马类的特点(有名字,颜色,还能奔跑),再具体到的卢和其它马的实例相比,能够日行千里。是谓之多态

优劣

构造函数的优势在于有了它标示类型(抽象出一个概念,比如Person,Horse),让计算机较好的理解操作者的意思。只要你对一个函数使用new来调用,它就是构造函数。

然而之前提到浪费资源的问题依然存在。就是声明对象实例时,每个方法都要在实例上重新创建一次。一次,不同实例下的同名方法是是不相等的。多见多个对象等于定义了很多次函数。 解决方案:把方法函数放到全局作用域中。

代码语言:javascript
复制
function Person(name,sex){
    this.name=name;
    this.sex=sex;
    this.showName=showName;
    this.showSex=showSex;
};

function showName(){
    alert('我的博客名是:'+this.name);
};
function showSex(){
    alert('我是'+this.sex+'的');
};

var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
console.log(person2.showName==person1.showName);

这时候返回的就是true了。很悲剧的是,我需要大量方法时,就得创建多个全局函数。封装的特点名不副实。

prototype——混合模式

prototype:原型

javascript中,只要是函数,都有一个隐藏的prototype属性。它指向一个对象,这个对象包含了所有实例都可以使用的对象和方法。 因此解决方案是:对象.prototype.新方法=函数

代码语言:javascript
复制
function Person(name,sex){
    this.name=name;
    this.sex=sex;
}
Person.prototype.showName=function(){
    alert('我的博客名是:'+this.name);
};
Person.prototype.showSex=function(){
    alert('我是'+this.sex+'的');
};

var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
person2.showSex();

换句话说,原型模式是给一(严格来说javascript没有类)的对象添加方法。我们可以对这个prototype加自定义的方法,实现所有对象共享一个方法。这种构造方法又有构造函数的属性,又有原型称为混合模式。

在js中: “类”:就是构造函数。没有功能,只能用来构造对象。 对象:是一个实例。

对一个实例定义定义了一个的属性名,这个属性会屏蔽掉对象中保存到prototype的同名属性,但不会修改原型对象的同名属性。但如果你用delete方法删除。原型内的属性又可以访问了。

代码语言:javascript
复制
Array.prototype.a=12;
var arr=[1,2,3];
console.log(arr.a);//12

arr.a=4;
console.log(arr.a)//4

delete arr.a;
console.log(arr.a)//12

注意:这里针对的是狭义的属性。而不是方法。

代码语言:javascript
复制
var str='1,2,3';
console.log(str.split(','));//['1','2','3']

str.split=function(n){return n};
console.log(str.split(','));//不会屏蔽!['1','2','3']

delete str.split;
console.log(str.split(','));//['1','2','3']

判断一个属性是属于原型还是实例,用hasOwnPrototy(属性名字符串)的方法。 person方法的最后小结: 看起来更像使用系统属性。减少了重载。但是prototype方法的优先级低于构造函数法。(类似class和行间样式的关系)


扩展阅读:关于原型

也许,你还可以把代码写的更好看一点。

代码语言:javascript
复制
function Person(name,sex){
    this.name=name;
    this.sex=sex;
};
Person.prototype={
    showName:function(){
        alert('我的博客名是:'+this.name);
    },
    showSex:function(){
        alert('我是'+this.sex+'的');
    }
};

var person1=new Person('葡萄美酒夜光杯','男');
var person2=new Person('欲饮琵琶马上催','女');
person2.showSex();

但是还不能满足,设想一个场景:

场景一:第三方js库里定义了Person对象,其中只有name和sex属性。(好像这个对象一无是处--||)
代码语言:javascript
复制
function Person(name,sex){
    this.name=name;
    this.sex=sex;
};

我想不修改源码,但是要扩展这个Person对象的功能,除了使其拥有showSex方法和showName方法外,还拥有一个新的属性:job。可以这么做: 通过设置prototype,构造一个Info对象。然后,new一个Info实例,把它赋给Person的prototype!

代码语言:javascript
复制
function Info(){
    this.job='coder';
}
Info.prototype={
    showName:function(){
        alert('我的博客名是:'+this.name);
    },
    showSex:function(){
        alert('我是'+this.sex+'的');
    }
};


function Person(name,sex){
    this.name=name;
    this.sex=sex;
};

Person.prototype=new Info();//关键步骤:Person的prototype竟然是Info的一个实例!

var person1=new Person('葡萄美酒夜光杯','男');

person1.showSex();//弹出 男
console.log(person1.sex)//男
console.log(person1.job)//coder
场景二:修改功能:

假设上你的js类库里已经存在了这么一个Person对象,已经实现了showSex和showName功能,还有job属性。我想修改它的showSex方法又不让改代码,可以直接把它覆盖掉:

代码语言:javascript
复制
Person.prototype.showSex=function(){
    alert('我是'+this.sex+'的'+','+'我的取向是'+this.job)
}

原型应用

以下举例两个简单的js方法的封装实现。

数组元素求和方法
代码语言:javascript
复制
var arr1=new Array(1,2,3,4);
Array.prototype.sum=function (){
    var result=0;
    for(var i=0;i<this.length;i++){
        result+=this[i];
    }
    return result;
};

var arr2=new Array(4,5,6,7)
console.log(arr1.sum())//10
console.log(arr2.sum())//22
console.log(arr1.sum==arr2.sum)//true

数组对象原生的方法中没有求和的方法,通过它,扩展了arr的内涵。

在IE6实现一个String对象的去头尾空格的trim方法:
代码语言:javascript
复制
String.prototype.trim=function(){
    return this.replace(/^\s+|\s+$/g,'');
}

var str=' saf dasafw grfe   ';
console.log(str.trim())//'saf dasafw grfe'

当浏览器不支持trim方法时,通过原型扩展了系统方法的功能。大大扩展了系统函数。 有了prototype,实例的被定义来的方法可以放一块。类和构造函数其实是一个东西。 构造函数在调用时需要new。所以构造函数首字母大写。 属性:每个对象不同,但方法所有对象都相同。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是对象,面向对象(Object-Orented,OO)的抽象
  • JS中的面向对象、面向对象的特点
    • 由鸭子模型到封装
      • 多态
        • 多态依赖于继承
          • 对象组成——属性和方法
          • 第一次面向对象
          • 没有对象?那就创建一个吧
            • 通过Object创建一个自己的对象
              • 工厂模式
                • 何以谓之工厂?
                • 缺点:(本质还是new的问题)
              • 构造函数模式
                • 构造函数的哲学
                • 优劣
              • prototype——混合模式
                • prototype:原型
                • 扩展阅读:关于原型
              • 原型应用
                • 数组元素求和方法
                • 在IE6实现一个String对象的去头尾空格的trim方法:
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档