写在前面

注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者。有发现什么问题的,欢迎留言指出。

起因

Object构造函数、对象字面量、Object.creat都可以用来创建单个对象,但有明显缺点:使用同一个接口创建很多对象,会产生大量的重复代码。所以才开始了创建对象的模式的探索。

检测对象的类

3种常见的检测任意对象的类的技术:instanceof运算符、constructor属性、构造函数的名字。3种各有优劣,适用于不同场景。(但往往我们更关注对象可以完成什么工作,对象属于哪个类并不是最重要的)

  • instanceof运算符

    运算符左边是对象,右边是构造函数,如o instanceof f,如果在o的原型链中查找到f,就返回true:

var date = new Date();
console.log(date instanceof Date);//true
console.log(date instanceof Object);//true
console.log(date instanceof Number);//false

这种方式的缺点:①无法通过对象来获得类名,只能检测对象是否属于指定的类名,②如果是不同的执行上下文,如客户端中每个窗口和框架子页面都具有单独的执行上下文,其中一个框架页面中的数组不是另一个框架页面的Array()构造函数的实例。

  • constructor属性
console.log(date.constructor == Date);//true

缺点:①和instance的第2点缺点一样,执行上下文的问题,②并不是所有的对象都有constrctor属性,如果创建对象时把对象的prototype直接覆盖了而又没有指定constrctor属性,就会没有这个属性。

  • 构造函数的名称
//返回对象的类
function classof(o) {
return Object.prototype.toString.call(o).slice(8,-1);
}
//返回函数的名字(可能是空字符串),不是函数就返回null
Function.prototype.getName = function () {
if("name" in this) return this.name;
return this.name = this.toString().match(/function\s*([^()]*)\(/)[1];
}
function type(o) {
//type,class,name
var t,c,n; //处理null值特殊情况
if(o === null) return 'null';
//处理NaN和它自身不相等
if(o !== o) return "nan";
//识别出原始值的类型
if((t = typeof o) !== "object") return t;
//识别出大多数的内置对象(类名除了"Object")
if((c=classof(o)) !== "Object") return c;
//如果对象构造函数的名字存在,就返回它
if(o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName()) ) return n;
//其他的类型无法判别,返回"Object"
return "Object";
}

这种方法的问题:①并不是所有的构造函数都有constructor属性,②并不是所有的函数都有名字(name是非标准属性),如果使用不带名字的函数定义表达式定义一个构造函数,getName()方法会返回空字符串。

//这种情况下如果没有name属性,那么getName()方法就返回空字符串了
var Example = function (x, y) {
this.x = x;this.y = y;
}

1.工厂模式

function creatPerson(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 = creatPerson('jaychou',34,'singer');
var person2 = creatPerson('xiaoming',15,'student');
//{name: "jaychou", age: 34, job: "singer", sayName: ƒ}
console.log(person1);
//{name: "xiaoming", age: 15, job: "student", sayName: ƒ}
console.log(person2);

工厂模式的最大缺点:没有解决对象识别的问题,不知道一个对象的类型。

2.构造函数模式

显然构造函数可用来创建特定类型的对象,如Array,Date等,重写上面的例子:

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
}
}
//{name: "jaychou", age: 34, job: "singer", sayName: ƒ}
var person1 = new Person('jaychou',34,'singer');
//{name: "xiaoming", age: 15, job: "student", sayName: ƒ}
var person2 = new Person('xiaoming',15,'student');

与之前对比:①没有显式地创建对象,②直接将属性和方法赋给了 this 对象,③没有 return 语句

使用new操作符调用Person构造函数后:

  • 创建一个新对象
  • 将构造函数的作用域赋给新对象(this指向了这个新对象)
  • 执行构造函数中的代码(为新对象添加属性和方法)
  • 返回新对象

类型的标识有了,构造函数模式的主要缺点是:每个方法都要在每个实例上重新创建一遍(函数也是对象的一种,导致重复创建对象了)

3.原型模式

  • 基本
function Person() {

}
Person.prototype.name = "jaychou";
Person.prototype.age = 34;
Person.prototype.job = 'singer';
Person.prototype.sayName = function () {
console.log(this.name);
}
var person1 = new Person();
person1.sayName();//jaychou
var person2 = new Person();
person2.sayName();//jaychou
console.log(person1.sayName == person2.sayName);//true

创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

//{name: "jaychou", age: 34, job: "singer", sayName: ƒ, constructor: ƒ}
console.log(Person.prototype);

我们打印了Person.prototype,默认情况下会有一个constructor属性,这个属性指向函数(在这里就是指向构造函数),其他的name,age,job,sayName属性和方法都是通过Person.prototype.添加进去的,所以由构造函数Person创建的实例都会包含这些属性和方法,它们是共享的。**

而且实例的内部包含一个指针(内部属性),指向构造函数的原型对象:[[Prototype]],在Firefox、Safari和Chrome中支持属性__proto__:

//{name: "jaychou", age: 34, job: "singer", sayName: ƒ, constructor: ƒ}
console.log(person1.__proto__);
console.log(Person.prototype == person1.__proto__);//true //打印对象的constructor:在这里person1的constructor就是Person
console.log(person1.constructor == Person);//true

另外,可以通过 isPrototypeOf 方法来确定对象之间是否存在原型关系:

console.log(Person.prototype.isPrototypeOf(person1));//true

还有,可以通过 Object.getPrototype 返回对象的原型:

//打印对象的原型:{name: "jaychou", age: 34, job: "singer", sayName: ƒ, constructor: ƒ}
console.log(Object.getPrototypeOf(person1));
console.log(Object.getPrototypeOf(person1).name);//jaychou

之前也有提及,查询属性和方法时先在当前实例中找,没有的话就到实例的原型链中找,而在实例中添加的属性会屏蔽原型中的同名属性。

  • 更简单的原型语法

上一个例子中每增加一个属性和方法就要敲一遍Person.prototype,比较麻烦,所以更简单的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:

Person.prototype = {
name:'jaychou',
age:34,
job:'singer',
sayName:function () {
console.log(this.name);
}
}
var person3 = new Person();
console.log(Person.prototype.constructor == Person);//false
console.log(Person.prototype.constructor == Object);//true
console.log(person3.constructor == Person);//false
console.log(person3.constructor == Object);//true

上面的代码将 Person.prototype设置为等于一个以对象直接量形式创建的新对象,结果就导致了constructor 属性不再指向 Person 了,因为本质上完全重写了默认的prototype对象,因此constructor 属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向 Person函数。看下面的例子就更清楚了:

function Teacher(){

};
var tea1 = new Teacher();
Person.prototype = tea1;
//true:因为Person.prototype被重写成tea1,tea1的constructor自然指向了Teacher构造函数
console.log(Person.prototype.constructor == Teacher);

所以,如果constrctor的值很重要,可以在上面代码的基础上手动加回去,最好仿照原生的把constructor属性设置成不可枚举的:

Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
console.log(Person.prototype.constructor == Person);//true

注意一个问题: 如果某个实例已经被创建了之后,再直接重写了构造函数的原型对象,那么之前已被创建好的对象内部的原型指针还是指向旧的原型,如果旧实例调用了新原型里面定义的方法,就会报错了。所以重写函数的原型对象时要特别注意这个问题。

  • 原型对象模式的问题

以上的创建对象在原型里共享了所有的属性和方法,对于方法还好,对于大多数情况下属性共享带来的问题就显而易见了。

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

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor:Person,
sayName:function () {
console.log(this.name);
}
}
var person1 = new Person('jaychou',34,'singer');
var person2 = new Person('xiaoming',15,'student');
person1.sayName();//jaychou
person2.sayName();//xiaoming
console.log(person1.sayName === person2.sayName);//true

5.动态原型模式(最常用)

对上面的例子进行视觉上的美化,希望把所有的内容都放在构造函数里面,可以通过检查某个应该存在的方法或属性是否存在,来决定是否需要初始化原型:

function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job; //添加共享的方法或属性
if(typeof this.sayName != "function"){
Person.prototype.sayName = function () {
console.log(this.name);
};
Person.prototype.sayJob = function () {
console.log(this.job);
}
}
}

注意: 使用动态原型模式时,不能使用对象直接量重写原型,原因上面已经解释过了,重写会切断了旧实例和新原型之间的联系。

js知识梳理3:创建对象的模式探究的更多相关文章

  1. js知识梳理4.继承的模式探究

    写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者.有发现什么问题的,欢迎留言指出 ...

  2. js知识梳理5:关于函数的要点梳理(1)

    写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者.有发现什么问题的,欢迎留言指出 ...

  3. js知识梳理6:关于函数的要点梳理(2)(作用域链和闭包)

    写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者.有发现什么问题的,欢迎留言指出 ...

  4. js知识梳理2:对象属性的操作

    1.属性的查询和设置 ①基本语法 这个简单,可以通过点(.)或方括号([])运算来获取属性的值,注意点运算符后的标识符不能是保留字,方括号内的表达式必须返回字符串或返回一个可以转换成字符串的值. va ...

  5. js知识梳理1:理解对象的属性特性

    1.数据属性 数据属性的4个特性: Configurable:①表示能否通过delete删除属性从而重新定义,②能否修改属性的特性,③能否把属性修改为访问器属性.对象直接量里默认值true. Enum ...

  6. JS知识梳理--图表

  7. Vue.js 2.x API 知识梳理(一) 全局配置

    Vue.js 2.x API 知识梳理(一) 全局配置 Vue.config是一个对象,包含Vue的全局配置.可以在启动应用之前修改指定属性. 这里不是指的@vue/cli的vue.config.js ...

  8. js基础知识梳理(最简版)

    基础的JavaScript知识,只放XMind截图.小白 JS01 JS02 JS03 最基础的js知识--!

  9. js基础梳理-如何理解作用域和作用域链?

    本文重点是要梳理执行上下文的生命周期中的建立作用域链,在此之前,先回顾下关于作用域的一些知识. 1.什么是作用域(scope)? 在<JavaScritp高级程序设计>中并没有找到确切的关 ...

随机推荐

  1. idea Mybatis mapper配置文件删除SQL语句背景色

    原样式: 看着很不爽 本文 idea 版本为:idea 2020.3.1,以下操作均基于此版本进行 解决办法 1.去除警告 Settings>Editor>Inspections>S ...

  2. Windows10 1809版本Windows自动更新服务无法禁用问题解决方案

    症状 Windows Update服务已经在服务管理器中禁用,但是莫名奇妙的会被自动设置为手动,并会自动下载补丁.原因 微软加强了系统更新服务的保护措施,导致按照原有的禁用服务方法,能够随时被恢复.解 ...

  3. Numpy库基础___一

    ndarray一个强大的N维数组对象Array •ndarray的建立(元素默认浮点数) 可以利用list列表建立ndarray import numpy as np list =[0,1,2,3] ...

  4. springcloud学习03-spring cloud eureka(上)

    对eureka一个大概介绍:https://blog.csdn.net/u010623927/article/details/88762525 这里面有个我做dubbo时的一个理解的错误:服务注册中不 ...

  5. 钓鱼+DNS欺骗学习笔记

    钓鱼+DNS欺骗学习笔记 0x00 写在前面 原文链接: http://www.cnblogs.com/hkleak/p/5186523.html 感谢大佬无私教学 0x01 步骤如下 第一步:布置钓 ...

  6. VS2012 生成可以在XP下运行的exe文件

    1. 在已安装VS2012条件下,安装update,作者已经安装了update3; 2. 相关设置: 设置"平台工具集":在项目右击-属性-常规-在"平台工具集" ...

  7. 高度不定,宽100%,内一div高不确定,如何实现垂直居中?

    verticle-align: middle; 绝对定位50%加translateY(-50%) 绝对定位,上下左右全0,margin:auto

  8. Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?

    Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询.在 Myba ...

  9. JVM组成结构以及各部分的功能

    Java虚拟机主要分为以下五个区: 一.方法区(METHOD AREA): 1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型 ...

  10. park和unpark

    1 介绍 LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语.LockSupport提供的两个主要方法就是park和unpark. park译为&quo ...