Javascript的高级特性--闭包,原型和继承。

1.闭包

闭包(closure)是函数式编程中的概念,出现于 20 世纪 60 年代,最早实现闭包的语言是 Scheme,它是 LISP 的一种方言。之后闭包特性被其他语言广泛吸纳。闭包的严格定义是“由函数(环境)及其封闭的自由变量组成的集合体。”这个定义对于大家来说有些晦涩难懂,所以让我们先通过例子和不那么严格的解释来说明什么是闭包。

什么是闭包

通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性,请看下面这个例子:

 1 var generateClosure = function() {
 2 var count = 0;
 3 var get = function() {
 4 count ++;
 5 return count;
 6 };
 7 return get;
 8 };
 9 var counter = generateClosure();
10 console.log(counter()); // 输出 1
11 console.log(counter()); // 输出 2
12 console.log(counter()); // 输出 3

这段代码中,generateClosure() 函数中有一个局部变量count,初值为 0。还有一个叫做 get 的函数,get 将其父作用域,也就是 generateClosure() 函数中的 count 变
量增加 1,并返回 count 的值。generateClosure() 的返回值是 get 函数。在外部我们通过 counter 变量调用了 generateClosure() 函数并获取了它的返回值,也就是 get 函
数,接下来反复调用几次 counter(),我们发现每次返回的值都递增了 1。

按照通常命令式编程思维的理解,count 是generateClosure 函数内部的变量,它的生命周期就是 generateClosure 被调用的时期,当 generateClosure 从调用栈中返回时,count 变量申请的空间也就被释放。

当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。上面例子中,当函数generateClosure() 的内部函数 get 被一个外部变量 counter 引用时,counter 和generateClosure() 的局部变量就是一个闭包

下面这段代码更加增进你的理解:

 1 var generateClosure = function() {
 2 var count = 0;
 3 var get = function() {
 4 count ++;
 5 return count;
 6 };
 7 return get;
 8 };
 9 var counter1 = generateClosure();
10 var counter2 = generateClosure();
11 console.log(counter1()); // 输出 1
12 console.log(counter2()); // 输出 1
13 console.log(counter1()); // 输出 2
14 console.log(counter1()); // 输出 3
15 console.log(counter2()); // 输出 2

2.原型

原型是 JavaScript 面向对象特性中重要的概念,也是大家太熟悉的概念。因为在绝大多数的面向对象语言中,对象是基于类的(例如 Java 和 C++ ),对象是类实例化的结果。而在JavaScript 语言中,没有类的概念,对象由对象实例化。

1 function Person() {
2 }
3 Person.prototype.name = 'BYVoid';
4 Person.prototype.showName = function () {
5 console.log(this.name);
6 };
7 var person = new Person();
8 person.showName();

上面这段代码使用了原型而不是构造函数初始化对象。这样做与直接在构造函数内定义属性有什么不同呢?

  • 构造函数内定义的属性继承方式与原型不同,子对象需要显式调用父对象才能继承构造函数内定义的属性。
  • 构造函数内定义的任何属性,包括函数在内都会被重复创建,同一个构造函数产生的两个对象不共享实例。
  • 构造函数内定义的函数有运行时闭包的开销,因为构造函数内的局部变量对其中定义的函数来说也是可见的。

原型链

JavaScript 中有两个特殊的对象: Object 与 Function,它们都是构造函数,用于生成对象。Object.prototype 是所有对象的祖先,Function.prototype 是所有函数的原
型,包括构造函数。我把 JavaScript 中的对象分为三类,一类是用户创建的对象,一类是构造函数对象,一类是原型对象。用户创建的对象,即一般意义上用 new 语句显式构造的对象。构造函数对象指的是普通的构造函数,即通过 new 调用生成普通对象的函数。原型对象特指构造函数 prototype 属性指向的对象。这三类对象中每一类都有一个 __proto__ 属性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到 Object.prototype。构造函数对象有 prototype 属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的 __proto__ 属性将会指向构造函数的 prototype 属性。原型对象有 constructor属性,指向它对应的构造函数。让我们通过下面这个例子来理解原型:

 1 function Foo() {
 2 }
 3 Object.prototype.name = 'My Object';
 4 Foo.prototype.name = 'Bar';
 5 var obj = new Object();
 6 var foo = new Foo();
 7 console.log(obj.name); // 输出 My Object
 8 console.log(foo.name); // 输出 Bar
 9 console.log(foo.__proto__.name); // 输出 Bar
10 console.log(foo.__proto__.__proto__.name); // 输出 My Object
11 console.log(foo. __proto__.constructor.prototype.name); // 输出 Bar

下图解析其中错综复杂的关系:

继承

在 JavaScript 中,继承是依靠一套叫做原型链(prototype chain)的机制实现的。属性继承的本质就是一个对象可以访问到它的原型链上任何一个原型对象的属性。例如上例的foo 对象,它拥有 foo. __proto__ 和 foo. __proto__.__proto__ 所有属性的浅拷贝(只复制基本数据类型,不复制对象)。所以可以直接访问foo.constructor(来自foo.__proto__,即Foo.prototype),foo.toString(来自foo. __proto__.__proto__,即Object.prototype)。

上面一段话,基本就是继承的精髓,理解就理解了。

posted on 2013-11-03 15:21  Mr.Vangogh  阅读(769)  评论(0)    收藏  举报

导航