阅读 3657

一个人就需要对象之js中八种创建对象方式

前言:说,点进来是不是喜欢我的笑容!!!大家好,我叫C乐,(恩,很直不gay)一名退役的大学生,喜欢摩旅。从上课的一次css接触后,便喜欢上了前端,以下是我的一些学习笔记,站在前辈们的肩膀上,分享一些我的理解,不足之处还请大家多多指教。性别男(哈哈哈哈哈哈,打不到我吧)

什么是JavaScript中对象?

js高级程序设计书中是说ECMA—262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数”。对象的每个属性或方法都有一个名字,而每个名字都能映射一个值,即名值对。内心感觉,这是啥啊(此处省略一万个字)

我第一次看到这句话的时候也是云里雾里。其实我们可以把对象当做一辆车来看待。一把车钥匙(函数或对象)匹配一辆车(基本值),建立这样一个基本概念后,我们继续理解数据属性和访问器属性就没那么难了。数据属性好比汽油,访问器属性好比发动机(放心,这辆车的发动10000公里内绝对不会漏机油,买了绝对不亏,好了言归正传),给车子加了汽油(数据类型),发动机(访问器属性有函数决定如何处理数据)就开了工作了,然后你就可以陪着你的另一半去浪漫的土耳其了,听说前端都是帅气漂亮工资又高的小哥哥小姐姐们,向前端大佬们学习。

有对象时,放假的时候可以有一个人陪着自己逛街,去吃想吃很久的美食,可以一起去看电影等等事情。

有对象时,遇到什么事情可以和对象撒娇,诉说委屈,即使事情已经解决了,还是可以得到对方的安慰。可以示弱,不必事事逞强的去做。

有对象时,即使两个人宅在家里也是很幸福的,可以做很多的事情,不管做什么事情都有另一个人可以分享,有时还会得到对方爱的夸奖。

有对象时,当外面下雨了,可以让对象来接自己,给自己送伞。或者在外面遇到事情都可以给对当打电话。

所以说有个对象是多么的重要。让我一起学习JavaScript中创建对象的方法(大佬您来请坐)

鉴于篇幅有点长,感兴趣的小伙伴可以收藏后慢慢看,路过的帅哥美女大神们请给个小赞,编写不易,小的给你打套擒敌拳给您助助兴。

八种创建对象的方式

Object构造函数模式(奔驰) 对象字面量模式(宝马) 工厂模式(奥迪) 构造函数模式(路虎) 原型模式(大众) 构造函数模式和原型结合(布加迪威龙) 动态原型模式 (劳斯莱斯) 寄生构造函数模式(迈凯伦)

  • 也许你会问为什么后面都加车品牌,因为我在每学会一种创建对象的方式后,仿佛我就拥有了这辆车,哈哈哈,原谅我幻想了下。

Object构造函数模式

 var person = new Object();
person.name = "Jhonha";
person.age = '27';
person.job = 'dancer';
person1.sayName = function () {
  console.log(this.name);
}
person1.sayName(); // Jhonha
console.log(person1.age); // 27
复制代码

构造函数在不返回值得情况下,默认会返回新对象实例。而通过构造函数末尾添加return语句,可以重写调用构造函数时候的返回值。

对象字面量模式

var person = {
    name: 'Jhonha',
    age: 22,
    job: 'dancer',
    sayName: function () {
      console.log(this.name);
    }
}
person2.sayName(); // dancer
复制代码

虽然Object构造函数和对象字面量都可以用来创建单个对象,但这些方法有个明显的缺点,就是一个接口创建很多对象,会产生大量的重复代码

工厂模式

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
      console.log(this.name);
    };
    return o;
}
var person1 = createPerson('Jhonha', 22, 'dancer');
var person2 = createPerson('Ciris', 22, 'Software engineer');
console.log(person1.name); // Jhonha
console.log(person2.name); // Ciris

优点:解决了创建多个相似对象的问题,函数封装创建对象,
无需写重复创建对象的代码
缺点:没有解决对象识别的问题(怎么样知道一个对象类型) 那要怎么办,
让我们继续往下学习吧。
复制代码

构造函数模式

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        console.log(this.name);
    }
}
var person1 = createPerson('Jhonha', 22, 'dancer');
var person2 = createPerson('Ciris', 22, 'Software engineer');
console.log(person1.name); // Jhonha
console.log(person2.name); // Ciris
复制代码

在这个例子中,Person()函数取代了createPerson()函数。我们注意到,Person()中的代码除了与createPerson()中相同的部分外,还存在以下不同之处

* 没有明显的创建对象
*  直接将属性和方法赋给了 this 对象
*  没有return语句
复制代码

要创建person的新实例,必须使用new操作符,经历一下四个步骤

* 创建一个新对象
* 将构造函数的作用域付给了新对象(因此this指向了这个新对象)
* 执行构造函数的代码(为这个新对象添加属性)
* 返回新对象
复制代码

在前面的例子的最后,person1 和 person2 分别保存着Person的一个不同的实例.这两个对象都有一个constructor(构造函数)属性,该属性指向Person。

console.log(person1.constructor === Person); // true
console.log(person2.constructor === Person); // true
复制代码

我们在这这个例子中创建的所有对象既是Object的实例,同时也是Person的实例,这一点可以通过instanceof操作符得到验证。

console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
复制代码

构造函数与其他函数唯一的区别,就在于调用他们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new操作符来调用,那他就可以作为构造函数。

// 当作构造函数使用
var person = new Person("Jhonha", 27, "dancer");
person.sayName(); //"Jhonha"

// 作为普通函数调用
Person("Ciris", 22, "software engineer"); // 添加到window
window.sayName(); //"Ciris"

// 在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
复制代码

构造函数模式优化

function Person(name) {
    this.name = name;
    this.sayName = sayName;
}

function sayName() {
    console.log(this.name);
}

var person1 = new Person('Jhonha');
console.log(person1.name);// Jhonha
var person2 = new Person('Ciris');
console.log(person2.name);// Ciris
复制代码

优点:解决了每个方法都要被重新创建的问题

缺点:person1和person2对象共用了在全局作用域的同一个sayName()函数,但是全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域优点名不副实,如果要对象要定义很多方法,就需要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。

谢谢您看到这里,看久了您就休息吧

原型模式

function Person(){}
Person.prototype = {
    constructor : Person,
    name: "Jhonha",
    age : 29 ,
    job: "Software Engineer",
    sayName: function(){
        return this.name;
    }
};
var person1 = new Person();
console.log(person1.name); // Jhonha
console.log(person1.age); // 29
console.log(person1.job); // Software Engineer
console.log(person1.sayName()); // Jhonha
复制代码

优点:我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

缺点:省略了为构造函数传递初始化参数,所有实例在默认情况下都将取得相同的值,当使用原型属性时会只要在一个实例上修改都会影响到所有的实例,例如在一个实例上修改数组。

附上一张图,希望能帮到大家加强理解构造函数,原型,实例。

图片作者

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

创建自定义类型是最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Jhonha","Nick"]
}

Person.prototype = {
    constructor: Person,
    sayName: function () {
        console.log(this.name);
    }
}
var person1 = new Person('bob', 22, 'frontend');
var person2 = new Person('lynn', 22, 'doctor');

person1.friends.push("Van");
console.log(person1.friends); // Jhonha Nick Van
console.log(person2.friends); // Jhonha Nick
console.log(person1.name); // bob
console.log(person2.name); // lynn
console.log(person1.sayName === person2.sayName); // true
复制代码

在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.friends(向其中添加了一个新的字符串),并不影响到person2.friends,因为他们分别来自不同的数组。

这种构造函数和原型混成的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建定义类型的方法。

谢谢您还在这里,感恩有你,给个赞可好,谢谢您

动态原型模式

function Person(name, age, obj) {
    this.name = name;
    this.age = age;
    this.obj = obj;

    console.log('typeof this.sayName: ', typeof this.sayName);
    // 检测sayName 是不是一个函数
    // 实际上只在当前第一次时候没有创建的时候在原型上添加sayName方法
    if (typeof this.sayName != 'function') {
        Person.prototype.sayName = function () {
            return this.name;
        }
    }
}

//因为构造函数执行时,里面的代码都会执行一遍,而原型有一个就行,不用每次都重复,所以仅在第一执行时生成一个原型,后面执行就不必在生成,所以就不会执行if包裹的函数,
//其次为什么不能再使用字面量的写法,我们都知道,使用构造函数其实是把new出来的对象作用域绑定在构造函数上,而字面量的写法,会重新生成一个新对象,就切断了两者的联系!

var person1 = new Person('bob', 22, 'frontend');
var person2 = new Person('lynn', 22, 'doctor');

console.log(person1.name); // bob
console.log(person2.name); // lynn
console.log(person1.sayName()); // bob
console.log(person2.sayName()); // lynn
console.log(person1.sayName === person2.sayName); // true

复制代码

看到这里也许会有朋友问:动态原型模式中使用if的作用?不使用if语句有什么问题或者弊端?

1.第一个问题:Person是一个构造函数,通过new Person(...)来生成实例对象。每当一个Person的对象生成时,Person内部的代码都会被调用一次。if的话,你每new一次(即每当一个实例对象生产时),都会重新定义一个新的函数,然后挂到Person.prototype.sayName属性上。而实际上,你只需要定义一次就够了,因为所有实例都会共享此属性的。所以如果去掉if的话,会造成没必要的时间和空间浪费;而加上if后,只在new第一个实例时才会定义sayName方法,之后就不会了。

2.第二个问题:假设除了:sayName方法外,你还定义了很多其他方法,比如:sayByecrysmile等等。此时你只需要把它们都放到对sayName判断的if块里面就可以了。

if (typeof this.sayName != "function") {
    Person.prototype.sayName = function() {...};
    Person.prototype.sayBye = function() {...};
    Person.prototype.cry = function() {...};
    ...
}
复制代码

这样一来,要么它们全都还没有定义(new第一个实例时),要么已经全都定义了(new其他实例后),即它们的存在性是一致的,用同一个判断就可以了,而不需要分别对它们进行判断。

哇,你还在呀,买个萌可好

寄生构造函数模式

寄生(parasitic)构造函数模式,其基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
        console.log(this.name);
    };
    return o;
}

var person1 = new createPerson('bob', 22, 'frontend');
var person2 = new createPerson('lynn', 22, 'doctor');

console.log(person1.name); // bob
console.log(person2.name); // lynn
console.log(person1 instanceof createPerson ) // false
console.log(person2 instanceof createPerson ) // false
复制代码

寄生构造函数模式,我个人认为应该这样读

寄生-构造函数-模式,也就是说寄生在构造函数的一种方法。

也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用 instanceof 都无法指向构造函数!

稳妥构造函数模式

function Person(name,age,job){
    //创建要返回的对象
    var o = new Object();

    // 可以在这里定义私有变量和函数

    //添加方法
    o.sayName = function(){
        console.log(name);
    };

    //返回对象
    return o;
}

var friend = Person("Nicholas", 29 , "dancer");
friend.sayName(); //Nicholas

复制代码

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象

与寄生构造函数模式有两点不同:

* 新创建的实例方法不引用 this
* 不使用 new 操作符调用构造函数
复制代码

稳妥对象最适合在一些安全的环境中。 稳妥构造函数模式也跟工厂模式一样,无法识别对象所属类型。

你都快看完了,最喜欢的一张图片送给你 给个赞可好,谢谢您

小补充

new操作符经历了啥?

  • 它创建了一个新的对象
  • 它会被执行[[Prototype]](也就是__proto__)链接。
  • 它使this指向新创建的对象
  • 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
  • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。

如何理解实例化

实例化的意思也差不多,你把一个类作为一个对象,就当成是车,你想开...所以你跟编译器(也就是狭义的电脑)请求,var person = new Person();就是一个跟机器借车开的语法,这里边Person和Contructor就是个名字的差异(构造函数的名字),比如说你爸爸喜欢跟他自己的车叫亲爱的,那么你开着车的时候别管是谁的,你也可以叫它为宝贝...Person()是不能改的,因为你得告诉机器你借的是哪台车...然后你借过来之后就随便你叫什么了....类下边的方法啊,公共属性都是可以借用过来的,好比说这台车有四个轮子一个方向盘就是属性,方法就是这台车可以正着开,也可以倒着开....所以无论你想正着开还是倒着开,你的前提都需要把车借过来才能开...实例化就是借车...调用方法就是借车之后的操作!

关注下面的标签,发现更多相似文章
评论