本文原发于个人我的博客,经屡次修改后发到sf上。本文仍在不断修改中,最新版请访问我的博客。javascript
最近工做一直在用nodejs作开发,有了nodejs,前端、后端、脚本全均可以用javascript搞定,非常方便。可是javascript的不少语法,好比对象,就和咱们经常使用的面向对象的编程语言不一样;看某个javascript开源项目,也常常会看到使用this关键字,而这个this关键字在javascript中因上下文不一样而意义不一样;还有让人奇怪的原型链。这些零零碎碎的东西加起来就很容易让人不知所措,因此,有必要对javascript这门语言进行一下深刻了解。前端
我这篇文章主要想说说如何在javascript中进行面向对象的编程,同时会讲一些javascript这门语言在设计之初的理念。下面让咱们开始吧。java
首先强调一下,咱们如今普遍使用的javascript都是遵循了ECMAScript 5.1标准的,正在制定中的版本为6.0,这个版本变化很大,增长了不少新的语法与函数,你们能够去Mozilla Developer Network上查看。node
javascript1.0 最初是由网景公司的Brendan Eich在1995年5月花了十天搞出来的,Eich的目标是设计出一种即轻量又强大的语言,因此Eich充分借鉴了其余编程语言的特性,好比Java的语法(syntax)、Scheme的函数(function)、Self的原型继承(prototypal inheritance)、Perl的正则表达式等。react
其中值得一提的是,为何继承借鉴了Self语言的原型机制而不是Java的类机制?首先咱们要知道:git
Javascript1.0的功能相对简单,为了在从此不断丰富javascript自己功能的同时保持旧代码的兼容性,javascript经过改变运行时的支持来增长新功能,而不是经过修改javascript的语法,这就保证了旧代码的兼容性。这也就是javascript选择基于运行时的原型机制的缘由。github
wikipedia这样描述到:JavaScript is classified as a prototype-based scripting language with dynamic typing and first-class functions。这些特性使得javascript是一种多范式的解释性编程语言,支持面向对象、命令式(imperative)、函数式(functional)编程风格。正则表达式
在javascript中,除了数字、字符串、布尔值(true/false)、undefined这几个简单类型外,其余的都是对象。编程
数字、字符串、布尔值这些简单类型都是不可变量,对象是可变的键值对的集合(mutable keyed conllections),对象包括数组Array
、正则表达式RegExp
、函数Function
,固然对象Object
也是对象。后端
对象在javascript中说白了就是一系列的键值对
。键能够是任何字符串,包括空串;值能够是除了undefined之外的任何值。在javascript中是没有类的概念(class-free)的,可是它有一个原型链(prototype linkage)。javascript对象经过这个链来实现继承关系。
javascript中有一些预约义对象,像是Object、Function、Date、Number、String、Array等。
javascript中的每种类型的对象均可以采用字面量(literal)
的方式建立。
对于Object对象,可使用对象字面量(Object literal)
来建立,例如:
var empty_object = {};//建立了一个空对象 //建立了一个有两个属性的对象 var stooge = { "first-name": "Jerome", "last-name": "Howard" };
固然,也能够用new Object()
或Object.create()
的方式来建立对象。
对于Function
、Array
对象都有其相应的字面量形式,后面会讲到,这里再也不赘述。
javascript中的每一个对象都隐式含有一个[[prototype]]
属性,这是ECMAScript中的记法,目前各大浏览器厂商在实现本身的javascript解释器时,采用的记法是__proto__
,也就是说每一个对象都隐式包含一个__proto__
属性。举个例子:
var foo = { x: 10, y: 20 };
foo这个对象在内存中的存储结构大体是这样的:
当有多个对象时,经过__proto__
属性就可以造成一条原型链。看下面的例子:
var a = { x: 10, calculate: function (z) { return this.x + this.y + z; } }; var b = { y: 20, __proto__: a }; var c = { y: 30, __proto__: a }; // call the inherited method b.calculate(30); // 60 c.calculate(40); // 80
上面的代码在声明对象b、c时,指明了它们的原型为对象a(a的原型默认指向Object.prototye,Object.prototype这个对象的原型指向null),这几个对象在内存中的结构大体是这样的:
这里须要说明一点,咱们若是想在声明对象时指定它的原型,通常采用Object.create()方法,这样效率更高。
除了咱们这里说的__proto__
属性,相信你们日常更常见的是prototype
属性。好比,Date对象中没有加几天的函数,那么咱们能够这么作:
Date.prototype.addDays = function(n) { this.setDate(this.getDate() + n); }
那么之后全部的Date对象都拥有addDays
方法了(后面讲解继承是会解释为何)。那么__proto__
属性与prototype
属性有什么区别呢?
javascript的每一个对象都有
__proto__
属性,可是只有函数对象
有prototype
属性。
那么在函数对象中, 这两个属性的有什么区别呢?
__proto__
表示该函数对象的原型prototype
表示使用new来执行该函数时(这种函数通常成为构造函数,后面会讲解),新建立的对象的原型。例如:
var d = new Date(); d.__proto__ === Date.prototype; //这里为true
看到这里,但愿你们可以理解这两个属性的区别了。
在javascript,原型和函数是最重要的两个概念,上面说完了原型,下面说说函数对象。
首先,函数在javascript中无非也是个对象,能够做为value赋值给某个变量,惟一不一样的是函数可以被执行。
使用对象字面量方式建立的对象的__proto__
属性指向Object.prototype
(Object.prototype
的__proto__
属性指向null
);使用函数字面量建立的对象的__proto__
属性指向Function.prototype
(Function.prototype
对象的__proto__
属性指向Object.prototype
)。
函数对象除了__proto__
这个隐式属性外,还有两个隐式的属性:
和对象字面量同样,咱们可使用函数字面量(function literal)
来建立函数。相似于下面的方式:
//使用字面量方式建立一个函数,并赋值给add变量 var add = function (a, b) { return a + b; };
一个函数字面量有四个部分:
一个函数在被调用时,除了声明的参数外,还会隐式传递两个额外的参数:this
与arguments
。
this在OOP中很重要,this的值随着调用方式的不一样而不一样。javascript中共有四种调用方式:
function invocation pattern。当函数不做为属性调用时,this指向全局对象,这是个设计上的错误,正确的话,内部函数的this应该指向外部函数。能够经过在函数中定义一个变量来解决这个问题。
var add = function(a, b) {return a+b;} var obj = { value: 3, double: function() { var self = this;//把this赋值给了self this.value = add(self.value, self.value); } } obj.double(); //obj.value如今为6
除了this外,函数在调用是额外传入的另外一个参数是arguments。它是函数内部的一个变量,包含函数调用处的全部参数,甚至包含函数定义时没有的参数。
var sum = function () { var i, sum = 0; for (i = 0; i < arguments.length; i += 1) { sum += arguments[i]; } return sum; }; sum(4, 8, 15, 16, 23, 42); // 108
须要注意的是,这里的arguments不是一个数组,它只是一个有length属性的类数组对象(Array-like),它并不拥有数组的其余方法。
关于对象,最后说一下数组,javascript中的数组和日常编程中的数组不大同样。
数组是一种在内存中线性分配的数据结构,经过下标计算出元素偏移量,从而取出元素。数组应该是一个快速存取的数据结构,可是在javascript中,数组不具有这种特性。
数组在javascript中一个具备传统数组特性的对象,这种对象可以把数组下标转为字符串,而后把这个字符串做为对象的key,最后对取出对应该key的value(这又一次说明了对象在javascript中就是一系列键值对)。
虽然javascript中的数组没有传统语言中的数组那么快,可是因为javascript是弱类型的语言,因此javascript中的数组能够存听任何值。此外Array有不少实用的方法,你们能够去MDN Array查看。
javascript也为数组提供了很方便的字面量(Array Literal)
定义方式:
var arr = [1,2,3]
经过数组字面量建立的数组对象的__proto__
指向Array.prototype。
在Java中,对象是某个类的实例,一个类能够从另外一个类中继承。可是在基于原型链的javascript中,对象能够直接从另外一个对象建立。
在上面讲解对象时,咱们知道了在建立一个对象时,该对象会自动赋予一个__proto__
属性,使用各类类型的字面量(Literal)
时,javascript解释器自动为__proto__
进行了赋值。当咱们在javascript执行使用new操做符建立对象时,javascript解释器在构造函数时,同时会执行相似于下面的语句
this.__proto__ = {constructor: this};
新建立的对象都会有一个__proto__
属性,这个属性有一个constructor
属性,而且这个属性指向这个新对象。举个例子:
var d = new Date() d.__proto__.constructor === Date //这里为true
若是new不是一个操做符,而是一个函数的话,它的实现相似于下面的代码:
Function.prototype.new = function () { // Create a new object that inherits from the constructor's prototype. var that = Object.create(this.prototype); // Invoke the constructor, binding –this- to the new object. var other = this.apply(that, arguments); // If its return value isn't an object, substitute the new object. return (typeof other === 'object' && other) || that; };
以前也说了,基于原型的继承机制是根据运行时的语义决定的,这就给咱们提供了很大的便利。好比,咱们想为全部的Array添加一个map函数,那么咱们能够这么作:
Array.prototype.map = function(f) { var newArr = []; for(i=0; i<this.length; i++) { newArr.push(f(this[i])); } return newArr; }
由于全部的数组对象的__proto__
都指向Array.prototype对象,因此咱们为这个对象增长方法,那么全部的数组对象就都拥有了这个方法。
javascript解释器会顺着原型链查看某个方法或属性。若是想查看某对象的是否有某个属性,可使用Object.prototype.hasOwnProperty
方法。
经过上面屡次讲解,但愿你们对对象在javascript中就是一系列的键值对
、原型
与函数
这三个概念有更加深入的认识,使用javascript来写前端、后端与脚本。在React.js 2015大会上,Facebook公布了即将开源的React Native,这意味着从此咱们能够用javascript来写IOS、Android的原生应用了,这可真是learn-once, write-anywhere
。相信随着ECMAScript 6的发布,javascript这门语言还会有一系列翻天覆地的变化,Stay Tuned。:-)