创建对象

通过Object构造函数或对象字面量创建单个对象

 这些方式有明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为了解决这个问题,出现了工厂模式。  

工厂模式

   考虑在ES中无法创建类(ES6前),开发人员发明了一种函数,用函数来封装以特定接口创建对象的细节。(实现起来是在一个函数内创建好对象,然后把对象返回)。    

function createPerson(name,age,job){var o=new Object();o.name=name;o.age=age;o.job=job;o.sayName=function(){alert(this.name);};return 0;
}var person1=createPerson("Nicholas",29,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
复制代码

构造函数模式

像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

function Person(name,age,job){this.name=name;this.age=age;this.job=job;this.sayName=function(){alert(this.name);};
}var person1=new Person(...);
var person2=new Person(...);
复制代码

 与工厂模式相比,具有以下特点:  

  •  没有显式创建对象;

  • 直接将属性和方法赋给了this对象;

  • 没有return语句;

  • 要创建新实例,必须使用new操作符;(否则属性和方法将会被添加到window对象)

  • 可以使用instanceof操作符检测对象类型

构造函数的问题:

构造函数内部的方法会被重复创建,不同实例内的同名函数是不相等的。可通过将方法移到构造函数外部解决这一问题,但面临新问题:封装性不好。

原型模式

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

 使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。    

function Person(){
}Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="...";
Person.prototype.sayName=function(){...
};var person1=new Person();
person1.sayName();//"Nicholas"
复制代码

更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,并重设constructor属性。

function Person(){
}Person.prototype={name:"...",age:29,job:"...",sayName:function(){...}
};Object.defineProperty(Person.prototype,"constructor",{enumerable:false,value:Person,
});
复制代码

 原型对象的问题:  

  •  他省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值,虽然这会在一定程度带来一定的不便,但不是最大的问题,最大的问题是由其共享的本性所决定的。
  • 对于包含基本值的属性可以通过在实例上添加一个同名属性隐藏原型中的属性。然后,对于包含引用数据类型的值来说,会导致问题。

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

这是创建自定义类型的最常见的方式。

 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。所以每个实例都会有自己的一份实例属性的副本,但同时共享着对方法的引用,最大限度的节省了内存。同时支持向构造函数传递参数。    

function Person(name,age,job){this.name=name;this.age=age;this.job=job;this.friends=["S","C"];
}Person.prototype={constructor:Person,sayName:function(){alert(this.name);}
};var person1=new Person(...);
复制代码

动态原型模式

function Person(name,age,job){this.name=name;this.age=age;this.job=job;if(typeof this.sayName!="function"){Person.prototype.sayName=function(){alert(this.name);};}
}
复制代码

 这里只有sayName()不存在的情况下,才会将它添加到原型中,这段代码只会在初次调用构造函数时才执行。这里对原型所做的修改,能够立刻在所有实例中得到反映。  

Object.create()

ES5定义了一个名为Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型,第二个参数对对象的属性进行进一步描述。

Object.create()介绍

Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法

Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;

obj:一个对象,应该是新创建的对象的原型。

propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProperties()的第二个参数一样)。注意:该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。

var o = Object.create(Object.prototype, {// foo会成为所创建对象的数据属性foo: { writable:true,configurable:true,value: "hello" },// bar会成为所创建对象的访问器属性bar: {configurable: false,get: function() { return 10 },set: function(value) {console.log("Setting `o.bar` to", value);}}
});
console.log(o);//{foo:'hello'}
var test1 = Object.create(null) ;
console.log(test1);// {} No Properties
因为在bar中设置了configurable 使用set,get方法默认都是不起作用,所以bar值无法赋值或者获取
这里的o对象继承了 Object.prototype  Object上的原型方法
我们可以 对象的 __proto__属性,来获取对象原型链上的方法 如:
console.log(o.__proto__);//{__defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, __lookupSetter__: ƒ, …}
console.log(test1.__proto__);//undefined
复制代码

通过打印发现, 将{}点开,显示的是 No Properties ,也就是在对象本身不存在属性跟方法,原型链上也不存在属性和方法,

new object()

var test1 = {x:1};var test2 = new Object(test1);var test3 = Object.create(test1);
console.log(test3);//{}
//test3等价于test5
var test4 = function(){
  
}
test4.prototype = test1;
var test5 = new test4();
console.log(test5);
console.log(test5.__proto__ === test3.__proto__);//true
console.log(test2);//{x:1}
复制代码
var test1 = {};
var test2 = new Object();
var test3 = Object.create(Object.prototype);
var test4 = Object.create(null);//console.log(test4.__proto__)=>undefined 没有继承原型属性和方法
console.log(test1.__proto__ === test2.__proto__);//true
console.log(test1.__proto__ === test3.__proto__);//true
console.log(test2.__proto__ === test3.__proto__);//true
console.log(test1.__proto__ === test4.__proto__);//false
console.log(test2.__proto__ === test4.__proto__);//false
console.log(test3.__proto__ === test4.__proto__);//false
复制代码

总结:使用Object.create()是将对象继承到__proto__属性上

var test = Object.create({x:123,y:345});
console.log(test);//{}
console.log(test.x);//123
console.log(test.__proto__.x);//3
console.log(test.__proto__.x === test.x);//truevar test1 = new Object({x:123,y:345});
console.log(test1);//{x:123,y:345}
console.log(test1.x);//123
console.log(test1.__proto__.x);//undefined
console.log(test1.__proto__.x === test1.x);//falsevar test2 = {x:123,y:345};
console.log(test2);//{x:123,y:345};
console.log(test2.x);//123
console.log(test2.__proto__.x);//undefined
console.log(test2.__proto__.x === test2.x);//false
复制代码

继承

我这里就介绍一种吧,剩下的可以去权威指南里看去

原型链

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原 型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每 个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型 对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的 原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数 的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实 例与原型的链条。这就是所谓原型链的基本概念。

实现原型链有一种基本模式,其代码大致如下。

function SuperType(){this.property = true;
}
SuperType.prototype.getSuperValue = function(){return this.property;
};
function SubType(){this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){return this.subproperty;};var instance = new SubType();
alert(instance.getSuperValue());
//true
复制代码

以上代码定义了两个类型:SuperType 和 SubType。每个类型分别有一个属性和一个方法。它们 的主要区别是 SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋给 SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原 来存在于 SuperType 的实例中的所有属性和方法,现在也存在于 SubType.prototype 中了。在确立了 继承关系之后,我们给 SubType.prototype 添加了一个方法,这样就在继承了 SuperType 的属性和方 法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图 6-4 所示。

原型链的问题

原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引 用类型值的原型。想必大家还记得,我们前面介绍过包含引用类型值的原型属性会被所有实例共享;而 这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原 型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。 下列代码可以用来说明这个问题

 function SuperType(){this.colors = ["red", "blue", "green"];}
function SubType(){
}
//继承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
复制代码

这个例子中的 SuperType 构造函数定义了一个 colors 属性,该属性包含一个数组(引用类型值)。 SuperType 的每个实例都会有各自包含自己数组的 colors 属性。当 SubType 通过原型链继承了 SuperType 之后,SubType.prototype 就变成了 SuperType 的一个实例,因此它也拥有了一个它自 己的 colors 属性——就跟专门创建了一个 SubType.prototype.colors 属性一样。但结果是什么 呢?结果是 SubType 的所有实例都会共享这一个 colors 属性。而我们对 instance1.colors 的修改 能够通过 instance2.colors 反映出来,就已经充分证实了这一点。

原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上, 应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上 前面刚刚讨论过的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。

借用构造函数

在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数 (constructor stealing)的技术(有时候也叫做伪造对象或经典继承)。这种技术的基本思想相当简单,即 在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象, 因此通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数,如下所示:

function SuperType(){this.colors = ["red", "blue", "green"];}function SubType(){
//继承了 SuperTypeSuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"
复制代码

代码中加粗的那一行代码“借调”了超类型的构造函数。通过使用 call()方法(或 apply()方法 也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。 这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。结果, SubType 的每个实例就都会具有自己的 colors 属性的副本了。

1. 传递参数

相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函 数传递参数。看下面这个例子。

function SuperType(name){this.name = name;
}
function SubType(){
//继承了 SuperType,同时还传递了参数 SuperType.call(this, "Nicholas");
//实例属性this.age = 29;
}
var instance = new SubType();
alert(instance.name);    //"Nicholas";
alert(instance.age);     //29
复制代码

以上代码中的 SuperType 只接受一个参数 name,该参数会直接赋给一个属性。在 SubType 构造 函数内部调用 SuperType 构造函数时,实际上是为 SubType 的实例设置了 name 属性。为了确保 SuperType 构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中 定义的属性。

2. 借用构造函数的问题

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结 果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

组合继承

组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的 技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方 法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数 复用,又能够保证每个实例都有它自己的属性。下面来看一个例子。

 function SuperType(name){this.name = name;this.colors = ["red", "blue", "green"];
}SuperType.prototype.sayName = function(){alert(this.name);}function SubType(name, age){
//继承属性
SuperType.call(this, name);this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();
//"red,blue,green,black"
//"Nicholas";
//29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();
//"red,blue,green"
//"Greg";
//27
复制代码

在这个例子中,SuperType 构造函数定义了两个属性:name 和 colors。SuperType 的原型定义 了一个方法 sayName()。SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数,紧接着 又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型 上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包 括 colors 属性,又可以使用相同的方法了

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继 承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。 9

原型式继承

可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅 复制。而复制得到的副本还可以得到进一步改造。

寄生式继承

与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强 对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问 题,可以将这个模式与组合继承一起使用。

寄生组合式继承

集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。

转载于:https://juejin.im/post/5caff3a8f265da035d0c6a71

js创建对象的几种方法及继承相关推荐

  1. JS创建对象的三种方法

    在JavaScript中,对象是一组无序的相关属性和方法的集合.所有的实物都是对象,例如,字符串.数值.数组.函数等. 下面我会介绍三种创建对象的方法. 一.通过字面量创建对象 其语法格式如下: va ...

  2. JS创建对象模式7种方法详解

    创建对象的几种模式 虽然Object构造函数或者字面量,都可以用来创建对象,但这些方式有明显的缺点:使用同一个接口创建很多对象,会产生大量的代码, 于是,工厂模式诞生了 1 工厂模式 工厂模式是广为人 ...

  3. js创建对象的几种方法

    工厂模式 工厂模式非常直观,将创建对象的过程抽象为一个函数,用函数封装以特定接口创建对象的细节.如下所示: function createStudent(name,sex,grade){ var o ...

  4. js创建对象的四种方法

    1.利用new Object()创建对象 2. 字面量创建对象 3.工厂模式创建对象 4. 利用构造函数创建对象 <script>//利用new Object()创建对象var obj1 ...

  5. 动态加载JS脚本的4种方法

    动态加载JS脚本的4种方法 2006-12-04 15:33 要实现动态加载JS脚本有4种方法: 1.直接document.write <script language="javasc ...

  6. JavaSE(二十五)——String与StringBuffer、StringBuilder的区别、创建对象的几种方法、Http和Https的区别

    文章目录 1. String与StringBuffer.StringBuilder的区别 2. Java创建对象的几种方法 3. Http和Https的区别 1. String与StringBuffe ...

  7. node.js取参四种方法req.body,req.params,req.param,req.body

    node.js取参四种方法req.body,req.params,req.param,req.body 参考:https://my.oschina.net/u/2519530/blog/535309 ...

  8. JavaScript创建对象的4种方法

    JavaScript 创建对象的4种方法 所谓对象就是用来帮助你完成一些事情是,对象是有特征和行为的,是具体特指的某一个事物.使用对象可以使程序更加整洁 通过Object()方法来创建 Object( ...

  9. js延迟加载的几种方法

    这是一个面试经常问到的问题:js的延迟加载方法 (js的延迟加载有助与提高页面的加载速度) 主要考察对程序的性能方面是否有研究,程序的性能是一个项目不断地追求的,通常也是项目完成后需要长期做的一件事情 ...

最新文章

  1. wireshark读写pcap文件_PCAP-file-analysis 利用wireshark捕获tcp ip数据包和pcap文件分析 - 下载 - 搜珍网...
  2. LeetCode:2. Add Two Numbers
  3. mysql 1449 : The user specified as a definer ('usertest'@'%') does not exist 解决方法 (grant 授予权限)...
  4. PHP数组的访问方法有几种,数组常用方法有哪些
  5. 更多核心、更大内存、更低成本 AMD皓龙6000欲成云计算基石
  6. 西门子宣布美国充电桩扩产计划
  7. 在哪里学python-python在哪学
  8. 【SLAM笔记】如何使用Eigen进行矩阵运算
  9. Discuz多配色纯论坛c22 utf8电脑版模板
  10. 写论文、搞科研、读大学必备的28款软件。
  11. csr蓝牙适配 linux,新款4.0蓝牙适配器 迷你4.0蓝牙适配器 Bluetooth CSR 4.0 Dongle
  12. 全国青少年软件编程等级考试标准 (预备级)1-4级
  13. pytorch+yolov3(4)
  14. 国外服务器 ping值不稳定,服务器ping值不稳定是什么原因?
  15. 视频去水印在线网站?
  16. echarts正负极柱状图
  17. 用蒲公英进行内测更新
  18. 春节大礼包!——海多PS游戏下载及PS模拟器EPSXE VGS设置指南
  19. 安全测试中sql注入测试思路
  20. 华为手机备份的通讯录是什么文件_华为手机资料备份与恢复教程(含联系人短信图片程序等)...

热门文章

  1. 为什么让A.I.“顶天立地”需要6个多月?
  2. AI实验室•西安站 教你用人脸识别打造爆款应用
  3. Facebook开源多款AI工具,支持游戏、翻译等
  4. 1.75亿美元!吴恩达第三锤:宣布成立AI基金AIFund
  5. 为什么阿里巴巴规定禁止超过三张表 join?
  6. Spring Boot 定义接口的方法是否可以声明为 private?
  7. 太强了!这款轻量级的数据库中间件完美解决了SpringBoot中分库分表问题
  8. System.currentTimeMillis()竟然存在性能问题,这我能信?
  9. 王敏捷 - 深度学习框架这十年!
  10. 别乱提交代码了,看下大厂 Git 提交规范是怎么做的!