面向对象的JavaScript

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

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

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

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

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

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

由鸭子模型到封装

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

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

多态

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

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

多态依赖于继承

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

对象组成——属性和方法

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

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

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

第一次面向对象

首先定义一个show方法

var arr=[1,3123,43,534];
arr.show=function(){
    alert(this.length);
}
arr.show();

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

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

function show () {
    alert(this);
}

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

window.show=function() {
    alert(this);
}

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

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

var obj=new Object();

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

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

工厂模式

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

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;//必须返回对象本身
}

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

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以示区别。

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来调用,它就是构造函数。

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

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.新方法=函数

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方法删除。原型内的属性又可以访问了。

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

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

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和行间样式的关系)


扩展阅读:关于原型

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

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属性。(好像这个对象一无是处--||)
function Person(name,sex){
    this.name=name;
    this.sex=sex;
};

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

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方法又不让改代码,可以直接把它覆盖掉:

Person.prototype.showSex=function(){
    alert('我是'+this.sex+'的'+','+'我的取向是'+this.job)
}

原型应用

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

数组元素求和方法

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方法:

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。所以构造函数首字母大写。 属性:每个对象不同,但方法所有对象都相同。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-05-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏tkokof 的技术,小趣及杂念

编程小知识之 JavaScript 数组拷贝

Array.map 可以对数组元素进行映射(map)操作,如果提供一个自身到自身的映射函数,我们便可以实现数组的拷贝了.

12340
来自专栏影子

java8 异步api、循环、日期

转载请注明出处:https://www.cnblogs.com/funnyzpc/p/10801470.html

20260
来自专栏大话swift

I Promise U 实践

上一篇我们基本解除到了Promise的概念,也了解了PromiseKit中的几个基常用的概念,这次我们就来个小实践:

10130
来自专栏图南科技

YII2通过composer优化vendor

在Yii2 中,vendor是composer下载的依赖库文件,官方的项目模板代码里只有其自己的项目文件,而其依赖的yii框架等类库,都记录在composer....

14240
来自专栏阿dai_linux

python-字符串处理

我们想把其中的日期改为美国日期格式'mm/dd/yyyy'.比如 2019-06-12 改成 06/12/2019 格式

12130
来自专栏码思客

科幻电影看多了 碰到多维数组 请冷静一下

说在前面的话:其实越是基础的知识,讲起来难度越大,因为越是基础,它就越偏向底层,你看得到的知识就那么多,但是你看不到的地方有大量的你暂时不需要知道的知识,所以只...

9630
来自专栏崔庆才的专栏

关于字符串处理,你真的全掌握了吗?

字符串处理是 Python 中最基本的操作之一了,但其实有些用法你真的可能没有注意到,这里分享一篇关于 Python 字符串处理的总结文章,希望对大家有帮助。

9630
来自专栏影子

js api 之 fetch、querySelector、form、atob及btoa

转载请注明出处: https://www.cnblogs.com/funnyzpc/p/11095862.html

13830
来自专栏大话swift

I Promise You

PromiseKit是一个简易的异步框架,让你更加自如的将精力集中去处理更加重要的事情上。PromiseKit是一个更加易学,更加容易掌控并且结果思路更加清晰,...

7420
来自专栏大话swift

vapor实现 安装包和历史版本查看

阅读VaporStyle指导之后突然感觉写代码思路变慢了很多,思路总是被终端,明显感觉不适应那种代码和思维方式,不过强制扭转之后感觉代码组织和易读性明显提高了,...

9240

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励