用愚公移山说明Javascript建立对象的各类姿式

  太行、王屋二山,方七百里,高万仞。本在冀州之南,河阳之北.......javascript

  嗯,按照惯例,第一句话就是骗大家点进来的。在读本文以前,但愿你对Javascript的原型和原型链有必定了解,这有助于你更好的理解本文,以前有写过一篇相关文章,点此阅读。但这并非必须的。java

  都退后,我要继续讲故事了。数组

  北山愚公者,年且九十,面山而居。app

var person = {
    name : '愚公',
    age: 90,
    address: '北山脚下',
    whereToLive: function () {
        alert(this.address)
    }
};

  ......北山愚公曰:“虽我之死,有子存焉;子又生孙,孙又生子;子又有子,子又有孙;子子孙孙无穷匮也”。函数

  看到这儿,问题来了,愚公的子子孙孙那么多,显然使用对象字面量去建立是不合理的。咱们介绍第一种建立方式。post

工厂模式

function createPerson (name, age, address){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.address = address;
    o.whereToLive = function () {
        alert(this.address)
    };
    return o;
}

var son = createPerson('愚小公', 30, '北山');
var grandSon = createPerson('愚小小公', 5, '北山');

  工厂模式比较明显的一个缺点就是因为生成并返回了一个中间对象,因此不能判断对象的类型。this

构造函数模式

function Person(name, age, address) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.whereToLive = function(){
            alert(this.address);
        }; 
}
var son = new Person('愚小公', 30, '北山');
var grandSon = new Person('愚小小公', 5, '北山');

  构造函数与普通函数没有异处,没有语法上的任何差异,只是在调用的时候使用了new关键字。因此咱们有必要说一下new到底干了什么:spa

  1. 建立一个新的中间对象
  2. 将构造函数的做用于赋给这个中间对象
  3. 执行构造函数中的代码
  4. 返回中间对象

  以这里的代码为例,实际上第二步和第三步的操做能够总结为Person.apply(newObject,arguments),这里顺便说一句bind与call/apply的一个区别,bind返回的是一个函数,call/apply是顺带把这个函数给执行了,返回的是执行后的结果。prototype

  那么,构造函数模式有什么问题呢,其实也是显而易见的,若是愚公有一千个子子孙孙,那么每一个子孙都会自带一个whereToLive的方法,显然这种作法不文艺范儿code

原型模式

function Person () {
    
}

Person.prototype.name = '愚公';
Person.prototype.age = 90;
Person.prototype.address = '北山';
Person.prototype.whereToLive = function () {
    alert(this.address); 
};

var son = new Person();
var grandSon = new Person();
son.name = '愚小公';
son.address = '山的那边';


son.whereToLive();   //  '山的那边'
grandSon.whereToLive();   //  '北山'

  咱们在son对象上试图修改address属性,而且彷佛看起来也修改为功了,可是没有影响到grandSon的属性。因此其实这两个address其实并不同。为何呢?咱们在作以下操做:

delete son.address;
son.whereToLive();   //  '北山'

  咱们删掉了son的address属性,这时候son的address又成了原型中定义的值。因此咱们在修改address属性的时候并无动到原型中的值,而是在这个对象上新建了一个属性。而且在试图获取这个属性的时候会优先返回对象上的属性值。咱们管这个现象叫属性屏蔽。

  另外多提一点,就是在读取对象属性的时候,首先会查看该对象自己有没有,没有的话会顺着原型链一直向上查找,若是达到原型链顶层都没有找到,则返回undefined。这里再穿插一个知识点。不少刚入门的开发者会犯这样的错误:

var a = {};
console.log(a.b.c)

  在没有校验b属性是否存在便去试图获取c属性。若是到了原型链的顶端都没有找到b,a.b的值则为undefined,因此获取undefined的c属性必定会报错。正确的作法是在不肯定是否存在对应属性的时候,应当先作判断。

  可是在写入基本类型属性的时候有所不一样,在当前对象没有找到要写入的属性时,不会向上查找,而是在当前对象里新建一个属性,这么作的缘由是防止污染其余对象的属性值。细心的你可能发现了我在开头的时候强调了基本类型属性。若是是引用类型会怎么样呢?

function Person () {
    
}

Person.prototype.name = '愚公';
Person.prototype.age = 90;
Person.prototype.address = ['北山'];
Person.prototype.whereToLive = function () {
    alert(this.address); 
};

var son = new Person();
var grandSon = new Person();
son.address.push('山的那边');

grandSon.whereToLive();   //  '北山','山的那边'

  这里又有一个小知识点,引用类型是存在堆内存中的,不一样地方的应用其实指向的是同一块堆内存。因此若是试图修改原型对象中的应用类型,会形成全局污染,这也就是原型模式的一个致命缺点。

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

  坐稳,我又要穿插新的知识点了。咱们能够采用简写的方式避免原型模式赋予原型对象方法时啰嗦的问题。

function Person(name, age, address) {
        this.name = name;
        this.age = age;
        this.address = address;
}
Person.prototype = {
    constructor : Person,  // 手动修改构造函数指向
    whereToLive : function () {
        alert(this.address); 
    },
    howOld : function () {
        alert(this.age); 
    }
}

  组合使用构造函数模式和原型模式的写法是否是同时规避掉了构造函数模式和原型模式的问题呢?既能够共享公用的函数,又可让每一个对象独享本身的属性。

  须要注意的是,咱们在重写Person.prototype的时候,实际上使得constructor指向了Object,因此我这里进行了手动修正。

寄生构造函数模式

function PersonList (name, age, address){
    var o = new Array();
    o.push.apply(o, arguments);
    o.consoleString = function () {
       return this.join(",");
    };
    return o;
}

var list = new PersonList('愚小公', '愚小小公');
alert(list.consoleString());

  是否是很眼熟,跟工厂模式如出一辙,只不过是在调用的时候使用了new关键字。利用这种模式,咱们能够为对象添加额外的能力。本例中,就是给数组添加一个自定义的方法,使其能够拥有咱们赋予的新能力。

结语

  实际开发中仍是得根据实际场景灵活运用,总有适合你的那一款。今天就聊到这,欢迎你们补充和指正。

相关文章
相关标签/搜索