JavaScript中的面向对象(object-oriented)编程

本文原发于我的个人博客,经屡次修正后发到sf上。本文仍在不停修正中,最新版请接见个人博客。

近来事情一向在用nodejs做开辟,有了nodejs,前端、后端、剧本全都可以用javascript搞定,非常轻易。然则javascript的许多语法,比方对象,就和我们常常运用的面向对象的编程言语差别;看某个javascript开源项目,也常常会看到运用this关键字,而这个this关键字在javascript中因上下文差别而意义差别;另有让人新鲜的原型链。这些零零碎碎的东西加起来就很轻易让人手足无措,所以,有必要对javascript这门言语举行一下深切相识。

我这篇文章主要想说说如安在javascript中举行面向对象的编程,同时会讲一些javascript这门言语在设想之初的理念。下面让我们最先吧。

起首强调一下,我们如今普遍运用的javascript都是遵照了ECMAScript 5.1规范的,正在制订中的版本为6.0,这个版本变化很大,增添了许多新的语法与函数,人人可以去Mozilla Developer Network上检察。

设想理念

javascript1.0 最初是由网景公司的Brendan Eich在1995年5月花了十天搞出来的,Eich的目的是设想出一种即轻量又壮大的言语,所以Eich充足自创了其他编程言语的特征,比方Java的语法(syntax)、Scheme的函数(function)、Self的原型继续(prototypal inheritance)、Perl的正则表达式等。

个中值得一提的是,为何继续自创了Self言语的原型机制而不是Java的类机制?起首我们要知道:

  • Self的原型机制是靠运行时的语义
  • Java的类机制是靠编译时的类语法

Javascript1.0的功用相对简朴,为了在今后不停丰富javascript自身功用的同时坚持旧代码的兼容性,javascript经由历程转变运行时的支撑来增添新功用,而不是经由历程修正javascript的语法,这就保证了旧代码的兼容性。这也就是javascript挑选基于运行时的原型机制的缘由。

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中有一些预定义对象,像是ObjectFunctionDateNumberStringArray等。

字面量(literal)

javascript中的每种范例的对象都可以采纳字面量(literal)的体式格局建立。

关于Object对象,可以运用对象字面量(Object literal)来建立,比方:

var empty_object = {};//建立了一个空对象
//建立了一个有两个属性的对象
var stooge = {
    "first-name": "Jerome",
    "last-name": "Howard"
};

固然,也可以用new Object()Object.create()的体式格局来建立对象。

关于FunctionArray对象都有其响应的字面量情势,背面会讲到,这里不再赘述。

原型链(prototype linkage)

javascript中的每一个对象都隐式含有一个[[prototype]]属性,这是ECMAScript中的记法,现在各大浏览器厂商在完成本身的javascript诠释器时,采纳的记法是__proto__,也就是说每一个对象都隐式包括一个__proto__属性。举个例子:

var foo = {
    x: 10,
    y: 20
};

foo这个对象在内存中的存储组织大抵是如许的:

《JavaScript中的面向对象(object-oriented)编程》

当有多个对象时,经由历程__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),这几个对象在内存中的组织大抵是如许的:

《JavaScript中的面向对象(object-oriented)编程》

这里须要申明一点,我们假如想在声明对象时指定它的原型,寻常采纳Object.create()要领,如许效力更高。
除了我们这里说的__proto__属性,置信人人寻常更罕见的是prototype属性。比方,Date对象中没有加几天的函数,那末我们可以这么做:

Date.prototype.addDays = function(n) {
    this.setDate(this.getDate() + n);
}

那末今后一切的Date对象都具有addDays要领了(背面解说继续是会诠释为何)。那末__proto__属性与prototype属性有什么区别呢?

javascript的每一个对象都有__proto__属性,然则只要函数对象prototype属性。

那末在函数对象中, 这两个属性的有什么区别呢?

  1. __proto__示意该函数对象的原型
  2. prototype示意运用new来实行该函数时(这类函数寻常成为组织函数,背面会解说),新建立的对象的原型。比方:

    var d = new Date();
    d.__proto__ === Date.prototype; //这里为true
    

看到这里,愿望人人可以明白这两个属性的区别了。

在javascript,原型和函数是最主要的两个观点,上面说完了原型,下面说说函数对象。

函数对象Function

起首,函数在javascript中不过也是个对象,可以作为value赋值给某个变量,唯一差别的是函数可以被实行。

运用对象字面量体式格局建立的对象的__proto__属性指向Object.prototype(Object.prototype__proto__属性指向null);运用函数字面量建立的对象的__proto__属性指向Function.prototype(Function.prototype对象的__proto__属性指向Object.prototype)。

函数对象除了__proto__这个隐式属性外,另有两个隐式的属性:

  1. 函数的上下文(function’s context)
  2. 完成函数的代码(the code that implements the function’s behavior)

和对象字面量一样,我们可以运用函数字面量(function literal)来建立函数。相似于下面的体式格局:

//运用字面量体式格局建立一个函数,并赋值给add变量
var add = function (a, b) { 
    return a + b;
};

一个函数字面量有四个部份:

  1. function关键字,必选项。
  2. 函数名,可选项。上面的示例中就省略了函数名。
  3. 由圆括号括起来的一系列参数,必选项。
  4. 由花括号括起来的一系列语句,必选项。该函数实行时将会实行这些语句。

函数挪用与this

一个函数在被挪用时,除了声明的参数外,还会隐式通报两个分外的参数:thisarguments

this在OOP中很主要,this的值跟着挪用体式格局的差别而差别。javascript中共有四种挪用体式格局:

  1. method invocation pattern。当函数作为某对象一个属性挪用时,this指向这个对象。this赋值历程发生在函数挪用时(也就是运行时),这叫做late binding
  2. 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
    
  3. construct invocation pattern。javascript是一门原型继续言语,这也就意味着对象可以直接从其他对象中继续属性,没有类的观点。这和java中的继续不一样。然则javascript供应了一种相似与java建立对象的语法。当一个函数用new来挪用时,this指向新建立的对象。这时候的函数一般称为组织函数。
  4. apply invocation pattern。运用函数对象的apply要领来实行时,this指向apply的第一个参数。

除了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中的数组和寻常编程中的数组不大一样。

数组对象Array

数组是一种在内存中线性分派的数据组织,经由历程下标计算出元素偏移量,从而掏出元素。数组应当是一个疾速存取的数据组织,然则在javascript中,数组不具备这类特征。

数组在javascript中一个具有传统数组特征的对象,这类对象可以把数组下标转为字符串,然后把这个字符串作为对象的key,末了对掏出对应当key的value(这又一次说清楚明了对象在javascript中就是一系列键值对)。

虽然javascript中的数组没有传统言语中的数组那末快,然则由于javascript是弱范例的言语,所以javascript中的数组可以寄存任何值。另外Array有许多有用的要领,人人可以去MDN Array检察。

javascript也为数组供应了很轻易的字面量(Array Literal)定义体式格局:

var arr = [1,2,3]

经由历程数组字面量建立的数组对象的__proto__指向Array.prototype。

继续Inheritance

在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。:-)

参考

    原文作者:jiacai2050
    原文地址: https://segmentfault.com/a/1190000002542644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞