【Javascript】彻底搞懂闭包(closure)和原型(prototype)

首先讲讲为什么会有闭包和原型。

Javascript的最初就是一款脚本语言(名字中带scrip就足以说明这一点),用来开发网页,所以语法简洁,没有面向对象语言的概念(多态,继承,封装。。。),甚至没有线程。

为了实现多态,继承和封装,我们看看js是如何实现这些OOP特性的。

  • 封装

封装的概念非常简单,就是将用户不需要知道的数据和方法隐藏起来,外部无法直接访问。在Java中,可以用private, protected关键字进行修饰。在JS中可以用闭包实现。来看一个例子:

var person = {
  fullName : "Jason Shapiro",
};

alert(person.fullName); // Jason Shapiro
person.fullName = "Jim White";
alert(person.fullName); // Jim White

//不小心将邮箱赋给fullName
person.fullName = [email protected];
alert(person.fullName); // [email protected]

这里person对象的fullName属性是暴露给用户的,你无法保证用户总是赋合法的名字。

为了解决这一问题,我们可以用闭包。在此之前,我们先开看看函数的作用域(Function Scope)的概念:

Variables declared in a function are hidden from any code outside of its definition. 

意思是,任何在函数体内部声明的变量对外部都是隐藏的。这正是我们需要的特性,于是我们将上述person 对象改造成: 

var person = function () {
 
  var fullName = "Jason Shapiro";
  var reg = new RegExp(/\d+/);
 
  var theObj = {
    setFullName : function (newValue) {
      if( reg.test(newValue) ) {
        alert("invalid name");
      }
      else {
        fullName = newValue; // Legal! The object has access to "fullName"
      }
    },
    getFullName : function () {
     return fullName; // Legal! The object has access to "fullName"
    }
  }; // End of the Object
};
person.getFullName(); // 无法访问,属于函数内部theObj对象的属性

我们已经比较接近了,但是我们无法访问函数内部的变量,因为函数内部声明的变量只能在函数内部访问。于是我们便引出闭包的概念。

a closure is an inner-scope which has access to all of the variables defined outside of its block, even after those variables would have normally “fallen out of scope.”

闭包是即使跳出函数体,仍然能够访问函数内部,闭包外部的变量。

var person = function () {
 
  var fullName = "Jason Shapiro";
  var reg = new RegExp(/\d+/);
 
  return { 
    setFullName : function (newValue) {
      if( reg.test(newValue) ) {
        alert("Invalid Name");
      }
      else {
        fullName = newValue;
      }
    },
    getFullName : function () {
     return fullName; 
    }
  }; // end of the return
}; 

var p = person();
alert(p.getFullName());   // Jason Shapiro
p.setFullName( "Jim White" );
alert(p.getFullName());  // Jim White
p.setFullName( 42 ); // Invalid Name; the name is not changed.
p.fullName = 42;     // Doesn't affect the private fullName variable.
alert(p.getFullName());  // Jim White is printed again.

 需要注意的一点时,内部函数访问的是被创建的内部变量本身,而不是它的拷贝。所以在闭包函数内加入 loop 时要格外注意。另外当然的是,闭包特性也可以用于创建私有函数或方法。

  • 继承(to be continue)

如何实现继承?这是通过JS的另一特性原型Prototype来实现的。

在OOP中,通过类的继承来实现代码的复用,通过实例化一个类可以创建许多对象,在JS中继承是通过原型实现的。

let user = function(name) {
    this.name = name;
    this.getName = function () {
        console.log(this.name);
    }
};

//为了避免下面比较name时,对值进行比较,这里故意传入了String对象
let user1 = new user(new String('KK'));
let user2 = new user(new String('KK'));

console.log(user1.name === user2.name);    //输出false
console.log(user1.getName === user2.getName); //输出false

在上述代码中,我们通过构造函数user,创建了两个对象。实际上是通过复制 构造函数user的原型对象 来创建user1和user2。原型对象中有个constructor指向了user函数,实际上还是通过这个构造函数来创建的对象。

假如不用原型(更准确地说原型对象中没有用户定义的属性),那么这两个对象就无法共享任何属性,对于这个例子来说,getName的逻辑是一样的,不需要两份getName,所有的user对象其实可以共享这个getName方法。这个逻辑非常像java类中的静态函数,只不过静态函数只能够调用静态变量和静态方法。但是在JS的世界里,可以通过将getName定义在原型中,已达到所有对象共享这个函数。

let user = function(name) {
    this.name = name;
};

user.prototype.getName = function () {
    console.log(this.name);
}

user.prototype.color = new String('White');

let user1 = new user(new String('KK'));
let user2 = new user(new String('KK'));

console.log(user1.name === user2.name);  //输出false
console.log(user1.getName === user2.getName); //输出true
console.log(user1.color === user2.color); //输出true

这里就一目了然了。在原型对象中定义的变量和方法能够被所有多个对象共享。原型的属性被对象共享,但是它不属于对象本身。

user1.hasOwnProperty('name');  //true;
user1.hasOwnProperty('getName');  //false;

这里要注意:原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。反之,如果对象的属性被修改,原型的对象中相同的属性并不会修改。

原型链

JS中任何对象都有原型,函数对象有原型(只不过函数不充当构造函数时,原型不起作用),普通的JS object也有原型,原型是一个object,它也有原型,这就构成了一个原型链,直到Object.prototype。Object.prototype的原型是null。

读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。

  • this

Why?

简而言之,JavaScript 允许在函数体内部,引用当前环境的其他变量。

由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

  • 多态

 

参考:

[1] encapsulation-in-javascript

你可能感兴趣的:(Javascript,闭包,closure,原型,prototype)