JavaScript JavaScript 面向对象与原型

whitecrow · 2014年02月22日 · 最后由 oceandull 回复于 2014年02月24日 · 4609 次阅读

首先放在下博客的原文网址:http://liusihao.com/post/77483884517/javascript

##1, 原型编程的优缺点

JavaScript 是一个基于对象的原型 (Prototype) 语言。

什么是原型?我们来看看下面的代码:

function People() {};

People.prototype = {
    name: "White Crow"
   age: "23"
   run: function () {
    return name + age
   };
};

var person = new People();
alert(person.run())     //White Crow23

这就是一个 JavaScript 构造原型和实例化原型的过程,乍一看和面向对象中构造类和实例化为对象的过程非常相似,但是原型编程和面向对象(OOP)编程的区别到底是什么呢?它会导致哪些问题呢?

首先原型是共享的,如果修改原型:

People.prototype = {
    age: "200"
}
var person2 = new People() 

再运行 person2.run(),则会返回 undefined,整个 People.prototype 都被覆盖了。 你不能给原型的属性附加不同的值。相反,OOP 中,每个对象的属性值都可以是不同的,OOP 中 person1.name 可以是 Jack,person2.name 可以是 Tom,原型编程中,person1.name 和 person2.name 都是同一个值,属性值是共享的。

这就是 JavaScript 中原型的缺点(但同时也是它最大的优点),属性和方法的共享。因此原型的数据抽象能力相比于 OOP 中的类差了很多。

有人问了,在 JS 的原型编程中,难道不能把值当做参数传给对象的属性吗?这样每个对象都可以拥有不同的属性值了。答案是不行,因为 People.prototype 的构造函数不能传参。

##2, 运用原型 + 构造函数组合模式解决内存和数据抽象问题。

为了解决传参的问题,可以组合用构造函数 + 原型模式

//不共享,使用构造函数,解决了数据抽象问题
function People(name, age){
   this.name = name;
   this.age = age;
};

//共享,使用原型模式,节约内存
People.prototype = {
     run: function(){
         return name + age
    };
};

为什么 run 要使用原型模式创建,因为原型方法和属性在内存中是共享的,可以用于节约内存,不会因为 new 一个对象,就开辟一块内存用于存放相同的方法。

这种模式解决了数据抽象和节约内存的问题,是比较好的解决方法。

##3, 动态原型模式

原型模式和构造函数的组合模式解决了这两个问题后,我们又发现了一个问题,那就是它们没有 OOP 中的封装性。看起来比较乱,没有将信息都封装在函数体内。 那么我们可以这么做,解决封装问题:

function People(name, age){
    this.name = name
    this.age = age
   People.prototype.run = function(){
    return this.name + this.age
   };
};

但是这么做,因为多次 new People 时,会初始化多次 run,为了不让它初始化多次,我们可以再添加一个条件判断语句解决这个问题:

function People(name, age){
    this.name = name
  this.age = age
  if (typeof this.run != 'function'){
        People.prototype.run = function(){
            return this.name + this.age
        };
    };
};

##4, JS 中对象查找属性和方法的链条。

JS 中,一个对象首先会查找它的构造函数的属性和方法

举例:

var box1 = {name : 'a'};
box1.name
//output 'a'
box1
//output Object {name: "a"}

var box1 = {name : 'a'}; 这一段实际是创建了一个构造函数为 Object,属性为 name: 'a'的对象。所以当你运行 box1 时,它会返回 Object {name: "a"}。

如果你这时候添加 box1 的构造函数 (Object) 的原型:

Object.prototype.name = "b"
Object.prototype.age = 99

box1.name
//output "a"
box1.age
//output 99

所以我们可以发现,一个对象是先从自己的构造函数来查找自己的属性,接着从构造函数原型中查找自己的属性。Object 中找得到 name,那么用 Object 的 name,Object 找不到 age,就用 Object.prototype 里的 age。

我们总结出一条查找属性的原则,就是“就近原则”,实例里有属性,就返回,没有才去查找原型,如果还没有则找到实例的类的超类,查找超类的属性,如果超类的属性没有,就查找超类的原型,一直往上到顶层的 Object 对象。

##5, 原型链

如果连原型里都找不到自己的属性呢,则解释器会继续向构造函数的构造函数的原型,直到上升到根函数的原型(Object.prototype)也找不到为止,这时候返回 underfined。我们来举个例子:

function People(name) = {
   this.name = name
}
People.prototype.age = 99
Object.prototype.height = 185
person = new People('Jack')

person.name //output 'jack'

person.age      //output 99

person.height   //output 185

person.weight   //undefined

这个就被称为 JS 的原型链。

##6, 继承 继承在 JS 中用原型链来实现:

function Box(){
    this.name = "Jack"
}
function Desk(){
    this.age = 100
}
Desk.prototype = new Box();  //Desk继承了Box,通过原型形成了链条。

var desk = new Desk();
alert(desk.name);   //Jack
alert(desk.age);      //100

function Table(){
   this.name = "Tom"
}

Table.prototype = new Desk();  //Table继承了Desk

var table = new Table();
alert(table.name); //output "Tom"

##7 对象冒充继承

待续

欢迎拍砖,如果觉得写得好,请点击下面的 喜欢 :D

1 楼 已删除
2 楼 已删除

#2 楼 @whitecrow 抱歉抱歉,太困眼花,又看了一遍貌似没什么错

##6 里面,如果你对 desk 调用一个不存在的方法,比如 desk.test(),会报错为 TypeError: Object # has no method 'test'

这个错不够严谨,因为 desk 的类型应该是,解决的方式是 desk.constructor = Desk

为了更简洁,可以把这句写在构造函数里面: function Desk(){ this.age = 100 this.constructor = Desk }

但是这样会让构造函数看起来奇怪,所以还可以这样: Desk.prototype = new Box(); Desk.prototype.constructor = Desk;

需要 登录 后方可回复, 如果你还没有账号请 注册新账号