大家好,我是苏日俪格,本文是面向对象的第二部分,纯属个人理解,有哪里不对的地方请在评论区指出,大家一起学习共同进步。
创建对象
在面试中,经常会被问到创建对象都有哪些方式,在创建单个对象的时候通常就用对象字面量,多个对象就用工厂模式、构造函数、原型模式和构造函数原型的混合模式
下面来逐个介绍一下:
对象字面量:
栗子如下:
let Person = { name: '苏日俪格', age: 24, job: '前端开发'} console.log(Person) // {name: "苏日俪格", age: 24, job: "前端开发"}
优点:通俗易懂,人人都会的一种简单的方法
缺点:只适用于创建单个对象,用同一个接口创建多个对象的话,就会有很多的冗余代码,为了解决这个缺点,我们使用工厂模式
工厂模式:
栗子如下:
function createPerson(name, age, job){ let obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.show = function(){ console.log(`姓名:${obj.name}, 年龄:${obj.age}, 工作:${obj.job}`); } return obj; }let person1 = createPerson('苏日俪格', 24, '前端开发'); person1.show(); // 姓名:苏日俪格, 年龄:24, 工作:前端开发let person2 = createPerson('赵云', 27, '救阿斗'); person2.show(); // 姓名:赵云, 年龄:27, 工作:救阿斗
优点:封装了一个函数解决了代码冗余的问题
缺点:无法明确创建的对象的类型,为了解决这个缺点,我们使用构造函数
构造函数:
栗子如下:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.show = function(){ console.log(`姓名:${this.name}, 年龄:${this.age}, 工作:${this.job}`); } }let person1 = new Person('苏日俪格', 24, '前端开发'); person1.show(); // 姓名:苏日俪格, 年龄:24, 工作:前端开发let person2 = new Person('赵云', 27, '救阿斗'); person2.show(); // 姓名:赵云, 年龄:27, 工作:救阿斗
和工厂模式的不同之处:
没有显式的创建对象(new Object())
没有return
直接将属性和方法赋给了this对象
Person是一个构造函数,首字母大写(这里注意,由于构造函数和普通函数的区别在于有无返回值,并不是大小写,小写也可以,但是为了语义化也算是行规,必须大写)
优点:由于两个实例共享了show这个全局的方法,就解决了两个函数做一件事的问题
缺点:如果定义了多个全局的函数,那么这个自定义的引用类型就丝毫灭有封装性可言了,而且每个方法都要在每个实例上重新创建一遍,为了解决这个缺点,我们使用原型模式
原型模式:
栗子如下:
function Person(){} Person.prototype.name = '苏日俪格'; Person.prototype.age = 24; Person.prototype.job = '前端开发'; Person.prototype.show = function(){ console.log(`姓名:${this.name}, 年龄:${this.age}, 工作:${this.job}`); }let person1 = new Person(); person1.show(); // 姓名:苏日俪格, 年龄:24, 工作:前端开发let person2 = new Person(); person2.show(); // 姓名:苏日俪格, 年龄:24, 工作:前端开发person2.name = '赵云'; person2.age = 27; person2.job = '救阿斗'; person2.show(); // 姓名:赵云, 年龄:27, 工作:救阿斗
优点:可以让所有对象的实例共享它所包含的属性和方法,不用再从实例中重新定义信息,直接将信息放在原型对象中
缺点:显而易见,所有实例都是共享的属性,但是实例一般会有自己单独的属性的,这种方法一般不用,那么最后一种就是结合了前面所有的缺点的一种方式,也是最让码农们认同的
这个时候有些人就想了,重复写那么多代码,我们可以简写成这样的啊:
function Person(){} Person.prototype = { name: '苏日俪格', age: 24, job: '前端开发', show: function(){ console.log(`姓名:${this.name}, 年龄:${this.age}, 工作:${this.job}`); } }let person1 = new Person(); person1.show(); // 姓名:苏日俪格, 年龄:24, 工作:前端开发let person2 = new Person(); person2.show(); // 姓名:苏日俪格, 年龄:24, 工作:前端开发person2.name = '赵云'; person2.age = 27; person2.job = '救阿斗'; person2.show(); // 姓名:赵云, 年龄:27, 工作:救阿斗
上面折中写法确实清晰了许多,但是这个是在原型模式的情况下,把构造函数的原型等于了以对象字面量的形式创建的对象,这个时候constructor属性就不再指向Person了,为了证实这一点来看一个小东西instanceof
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
这句话是什么意思呢?
来看一个语法:object instanceof constructor
就是用instanceof来检测一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上,即左侧的对象是右侧类的实例
字面理解: constructor.prototype 是否存在于object 的原型链上
在上面加上这四行代码:
console.log(person1 instanceof Person) // trueconsole.log(person1 instanceof Object) // trueconsole.log(person1.constructor == Person) // falseconsole.log(person1.constructor == Object) // true
很明显,我们要的效果出来了,实例的构造函数已经由Person指向了Object,这个时候需要在代码里加上constructor的指向
Person.prototype = { constructor: Person, name: '苏日俪格', age: 24, job: '前端开发', show: function(){ console.log(`姓名:${this.name}, 年龄:${this.age}, 工作:${this.job}`); } }
构造函数原型的混合模式:
栗子如下:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; } Person.prototype = { constructor: Person, show: function(){ console.log(`姓名:${this.name}, 年龄:${this.age}, 工作:${this.job}`); } };let person1 = new Person('苏日俪格', 24, '前端开发'); person1.show();let person2 = new Person('赵云', 27, '救阿斗'); person2.show();console.log(person1.name == person2.name) // falseconsole.log(person1.show == person2.show) // true
做了个实验,看看两个实例到底是怎样的,共享的方法得到了验证,两者的属性并不是共享的,因为在创建实例的同时,系统开辟了单独的内存给它,每个实例也都会给自己的属性创建一个副本,所以他们之前是互不影响的
优点:可以通过构造函数模式来定义实例所需要的属性,用原型来定义实例共享的属性和方法(谨记:本身自带的属性的权重始终高于原型定义的属性),分工明确
对象创建好了,关键的地方来了,在对象继承之前先要搞明白__proto__和prototype的关系,这个懂了,就可以玩原型链继承了_
本文的所有内容均是一字一句敲上去的,希望大家阅读完本文可以有所收获,因为能力有限,掌握的知识也是不够全面,欢迎大家提出来一起分享!谢谢O(∩_∩)O~
作者:苏日俪格
链接:https://www.jianshu.com/p/b2e2c0c56e06