资讯专栏INFORMATION COLUMN

用愚公移山说明Javascript创建对象的各种姿势

cartoon / 2785人阅读

摘要:北山愚公者年且九十面山而居。工厂模式愚小公北山愚小小公北山工厂模式比较明显的一个缺点就是由于生成并返回了一个中间对象,所以不能判断对象的类型。

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

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

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

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

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

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

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

工厂模式
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, "北山");

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

构造函数模式
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到底干了什么:

创建一个新的中间对象

将构造函数的作用于赋给这个中间对象

执行构造函数中的代码

返回中间对象

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

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

原型模式
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关键字。利用这种模式,我们可以为对象添加额外的能力。本例中,就是给数组添加一个自定义的方法,使其可以拥有我们赋予的新能力。

结语

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107955.html

相关文章

  • database

    摘要:它是第一个把数据分布在全球范围内的系统,并且支持外部一致性的分布式事务。目的是使得开发者阅读之后,能对项目有一个初步了解,更好的参与进入的开发中。深度探索数据库并发控制技术并发控制技术是数据库事务处理的核心技术。 存储过程高级篇 讲解了一些存储过程的高级特性,包括 cursor、schema、控制语句、事务等。 数据库索引与事务管理 本篇文章为对数据库知识的查缺补漏,从索引,事务管理,...

    csRyan 评论0 收藏0
  • #yyds干货盘点#“愚公移山方法解atoi,自以为巧妙!

    摘要:若函数不能执行有效的转换,返回。如果数值超过可表示的范围,则返回或。示例输入输出解释转换截止于数字,因为它的下一个字符不为数字。 这是我参与11月更文挑战的第12天。一、写在前面LeetCode 第一题两数之和传输门:听说你还在写双层for循环解两数之和?LeetCode 第二题两数之和传输门:两个排序数组的中...

    番茄西红柿 评论0 收藏2637
  • 白鹭引擎王泽:重度H5游戏性能优化技巧标题文章

    摘要:据不完全统计,这五年中,白鹭引擎累计运转的游戏和微信小游戏的流水数据约为亿。 我们的引擎架构师做某一沙龙活动的演讲速记,纯纯的干货,分享给大家。 王泽:各位开发者下午好!我叫王泽,是白鹭引擎的首席架构师。 今天给大家分享的题目是《重度H5游戏性能优化技巧》。之所以决定用这个题目,是因为我最近几周在广深一带拜访了很多使用白鹭引擎的开发者,发现特别是在广州一带,大部分开发者都在做重度H5游...

    xbynet 评论0 收藏0
  • JavaScript 设计模式 ① 正确使面向对象编程姿势

    javascript是一门弱语言,他有着分同一般的灵活性使它迅速的成为几乎人人必会的一门语言,but,你们使用的姿势真的正确吗? 在以前的开发过程当中,老板:给我加个验证用户邮箱、验证用户短信...功能! function checkMessage(){...} function checkEmail(){...} function ... //茫茫多的函数 这样写好了之后 function 是全...

    macg0406 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<