pythonWeb前端之路首页投稿(暂停使用,暂停投稿)

JavaScript神奇的面向对象特性(上)

2017-01-19  本文已影响168人  dongwenbo

本文写作时长5小时

OO语言都有一个特点,就是都存在类的概念。通过类自定义类型,创建对象实例。和这些OO语言不同,JavaScript中是没有类的,又或者说原型对象就是JavaScript中的类。但是没有类的JavaScript是如何创建对象呢?退一步讲,在JavaScript中,对象到底是什么?

理解对象的本质

ECMA中关于对象的定义

对象就是无序属性的集合,属性的值可以是基本值,函数或者其他对象

通过JavaScript提供的Ojbect()函数,可以创建出一个简单的对象实例

var person = new Object();
person.name = "fuckJapan";
person.age = 21;
person.sayHello = function () {
    alert(this.name)
}

创建了一个person对象,person有三个属性,其中nameage是基本值,sayHello是一个函数。

这种写法可以用字面值对象重写为:

var person = {
    name:"fuckJapan",
    age:21,
    sayHello:function () {
        alert(this.name)
    }
};

这种语法看起来更加 封装

上面两种创建对象的方式,有点像手工作坊,每创建一次对象都要重新给对象添加属性,赋值,不胜其烦。怎么解决?进工厂,上生产线。

工厂模式

工厂模式抽象出创建对象的工程。既然JavaScript中没有类,所以就用函数代替,类似这样:

function createPerson(name, age, job) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.sayHello = function () {
        alert(this.name);
    }
    return obj;
}
var person1 = createPerson("fuckJapan1",21,"student");
var person2 = createPerson("fuckJapan2",22,"teacher");

有了生产线,工人们再也不用亲自动手了,直接把原料放入函数,duang!一个person,两个person,统统new出来。工人解放了双手,提高了生产力,效率蹭蹭上升。

我是谁,我来自哪里?

虽然工厂模式解决了创建多个类似对象的问题,但是却没有解决对象识别的问题。怎么办?

构造函数模式

构造函数和普通的函数没有任何区别,只是用new调用时会产生特殊的效果。另外构造函数有一些约定的规定,比如首字母大写。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayHello = function () {
        alert(this.name);
    }
}
var person1 = new Person("fuckJapan1",21,"student");
var person2 = new Person("fuckJapan2",22,"teacher");

这种模式中,函数中并没有显示创建对象,而是使用了 this 关键字给对象实例添加属性。在构造函数的结尾也没有return。在调用时,使用了new关键字。在用new调用构造函数时,一共做了这么几件事:

为什么这种模式解决了对象识别的问题呢?别着急,这是因为在person1person2中还包含一个名为constructor的属性,这个属性指向构造函数Person。从此person1person2不再是三无产品,是可溯源的正品,它们是由一个名为Person的构造工厂生产的。

alert(person1.constructor == Person)//true
alert(person2.constructor == Person)//true
alert(person1.constructor == person2.constructor)//true

alert(person1 instanceof Person)//true
alert(person2 instanceof Person)//true
alert(person1 instanceof Object)//true
alert(person2 instanceof Object)//true

person1person2为什么都是Object类型,这是因为在JavaScript中所有的对象都有一个共同的祖先Object

不走寻常路,把构造函数当做普通函数调用

Person("fuckJapan",23,"student");
alert(window.name);//fuckJapan

由于如果在浏览器中执行,则构造函数中的属性被添加到window对象中

指定作用域

var obj = new Object();
Person.call(obj,"fuckJapanAgain",25,"student");
alert(obj.name);//fuckJapanAgain
alert(obj instanceof Person)//false

这种偷鸡摸狗的调用方法,就会导致生产出来的对象没有厂家

构造函数虽然解决了对象的识别问题,但是却没有解决对象方法的重复创建问题,person1person2中的sayHello方法是两个完全不同的方法,这是完全没有必要的

alert(person1.sayHello == person2.sayHello)//false

补救措施,将方法拿到外面,指向全局函数

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayHello = sayHello;
}

function sayHello() {
    alert(this.name);
}

暂时解决了方法重复创建的问题,可是,把自己的方法拿到外面这种方式总是不妥的,全局作用域中的函数却是特定对象的方法,这太诡异了。而且方法少了还好说,多了就太乱了。

原型模式

每个函数都有一个名为prototype的属性,指向函数的原型对象,类似OC中的类对象。原型对象中保存着创建对象实例共享的方法和属性。

function Person() {
}
Person.prototype.name = "fuckJapan";
Person.prototype.age = 29;
Person.prototype.job = "student";
Person.prototype.sons = ["son1","son2"];
Person.prototype.sayName = function () {
    alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
alert(person1.name);//fuckJapan
alert(person2.name);//fuckJapan
alert(person1.sayName == person2.sayName);//true

构造函数变成了空函数,原型对象可以看做是对象的模板,所有对象生成时,默认都有这些属性和对应的值,其中引用类型是所有对象共享,非引用类型都是独立的存储。这样虽然解决了每个对象实例方法重新创建的问题,但又带来了新的问题,数据紊乱,其实不光这样一个问题,还有创建出的对象属性还需要一个个赋值,构造函数根本没什么用

person1.name = "fuckJapanAgain";
alert(person1.name);//fuckJapanAgain
alert(person2.name);//fuckJapan

基本类型的并没有受影响

person1.sons.push("son3");
alert(person1.sons);//son1,son2,son3
alert(person2.sons);//son1,son2,son3

由于引用类型是所有对象共享,所以数据乱了

说了这么多了,这个也有问题,那个也有问题,那么到底该怎么做呢?

组合使用构造函数和原型模式

在构造函数中创建每个对象需要单独使用的属性,比如sons。在原型中创建所有对象共享的属性,比如sayHello

对象实例中在构造函数中创建的属性,每个都保留单独的副本,而在原型中创建的引用类型属性都共享同一份引用。同时这种模式还支持传递参数,可谓一举两得。

function Person(name,age,job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sons = ["son1","son2"];
}

Person.prototype = {
    constructor:Person,
    sayHello:function () {
        alert(this.name);
    }
}

var person1 = new Person("fuckJapan1",21,"student");
var person2 = new Person("fuckJapan2",26,"teacher");

person1.sons.push("son3");
alert(person1.sons);//son1,son2,son3
alert(person2.sons);//son1,son2

alert(person1.sayHello == person2.sayHello);//true

所有问题差不多都几乎是完美解决,这就是JavaScript中神奇的『类』。

除此之外,还有动态原型模式寄生构造函数模式稳妥构造函数模式。读者自己去研究。

说到面向对象,必须要说的就是继承,JavaScript中的「类」已经很神奇了,作为面向对象特性之一的继承则更加神奇,各种花式继承干到你哭。

欲知继承如何,且听下回分解...

上一篇下一篇

猜你喜欢

热点阅读