[关闭]
@FunC 2017-03-04T14:01:56.000000Z 字数 4658 阅读 2370

JavaScript中对象的创建与继承

JavaScript


回顾


插播一则ES6更新

ES6中的class

class的继承

使用extends关键字
constructor中使用super关键字使this绑定到父类的实例


Object对象


从创建对象开始

1.创建单个对象

  1. //早期方法,现在很少用
  2. var person = new Object();
  3. person.name = "John";
  4. //对象字面量语法,直观
  5. var person = {
  6. name: "John"
  7. }

存在问题:当需要创建多个对象时,要写大量重复代码

2.创建多个对象

2.1工厂模式
将创建对象的过程封装成一个函数

  1. function createPerson(name){
  2. var o = New Object();
  3. o.name = name
  4. return o;
  5. }
  6. var person1 = createPerson("John");
  7. var person2 = createPerson("Mike");

工厂模式的问题:创建的对象没有其类型的名字,所以无法判断两个对象是否为同一个类型

2.2构造函数模式
为了解决对象类型的问题,可使用构造函数来创建对象,该方法的核心在于使用了new操作符
来看看对函数使用new操作符会发生什么

  1. 创建一个新的对象
  2. 将构造函数的作用域赋给新对象(因此this指向了这个新对象)
  3. 执行构造函数中的代码
  4. 返回创建的新对象

于是我们就可以把构造函数写成如下形式:

  1. function Person(name){
  2. this.name = name;
  3. this.sayName = function(){
  4. alert(this.name);
  5. }
  6. }
  7. var person1 = new Person("John");
  8. var person2 = new Person("Mike");

注意其实不用new操作符这个函数也能跑,只是this指针就指向了windows对象了;为了表明这是我们定义的一种对象类型,函数名的首字母要大写(毕竟关键字都是function,容易和一般的函数混淆)
这样,person1和person2分别保存着Person的一个不同的实例,而且两个对象都有一个constructor属性,该属性指向构造函数Person,如下所示:

  1. alert(person1.constructor == Person);//true
  2. alert(person2.constructor == Person);//true

构造函数的问题:
直接看例子

  1. console.log(person1.sayName == person2.sayName);//false

可以看到,用上述方法定义的方法是两个不同的对象实例

  1. this.sayName = New Function(console.log(this.name));//这种写法等价于上述写法

然而这个方法的功能是一样的,创建不同的实例是没有必要的。所以调用方法时,其实只要找到对应的函数即可,而不是调用时创建一个函数。那么我们其实可以把函数写在构造函数外面:

  1. function Person(name){
  2. //...
  3. this.sayName = sayName;
  4. }
  5. function sayName(){
  6. console.log(this.Name);
  7. }

新的问题来了:这些定义在全局作用域的函数只能被某些对象使用,似乎失去了全局作用域的意义。另一方面,各种构造函数的方法都混杂着定义在全局当中,对构造函数来说毫无封装性可言。

2.3原型模式
每一个函数都有prototype属性,属性值是指向其原型对象的指针(对应的原型对象有constructor属性指向构造函数)。该原型对象的属性和方法由所有实例共享。
所以推荐把方法定义在原型上
注意:如使用对象字面量改写原型:

  1. Person.prototype = {
  2. this.name = name;
  3. }

实质上变更了Person.prototype指针的指向,而不是在原来的基础上修改,因此也丢失了constructpr属性。

2.4组合使用构造函数模式和原型模式(推荐)

综上,创建自定义类型时,建议方法定义在原型上,属性定义在构造函数当中。
加强版(定义在原型上的方法也写进构造函数中):

  1. function Person(name, age, job){
  2. //属性
  3. this.name = name;
  4. this.age = age;
  5. this.job = job;
  6. //方法
  7. //含多个方法时也只需要一条if语句,因为能通过表示初始化成功
  8. if( type of this.sayName != 'function'){
  9. Person.prototype.sayName = function(){
  10. alert(this.name);
  11. }
  12. }
  13. }

其它构造函数模式

2.5寄生构造函数模式(其实是工厂模式,但可以用new
试想我们要构建一个具有特殊功能的数组对象,要怎么做?

  • 不建议修改原生对象的原型。在不同环境下可能发生明明冲突;还有可能意外重写原生方法。
  1. function specialArray(){
  2. //创建数组
  3. var values=new Array();
  4. //添加值
  5. //注意这里用apply是为了把arguments这个伪数组拆开,否则等于把arguments这个对象整体作为values数组的一项
  6. //ES6中可以用拓展运算符代替,更直观,即values.push(...arguments);
  7. values.push.apply(values,arguments);
  8. //添加方法
  9. values.toPipedstring=function(){
  10. return this.join("|");
  11. }
  12. //返回数组
  13. return values;
  14. }
  15. var colors=new specialArray('red','yellow','green');
  16. alert(colors.toPipedstring()); // red|yellow|green

其实就是可用new的工厂模式,工厂模式的缺点它都有,用instanceof运算符也只能返回Object,尽量不建议使用这种模式;

2.6稳妥构建函数模式(安全)
稳妥对象:即无公共属性,同时其方法不引用this的对象。
即传入参数直接用于构建方法,不在属性上停留。如:

  1. function Person(name){
  2. var o = new Object();
  3. 0.sayName = function(){
  4. alert(name);
  5. };
  6. return o;
  7. }

继承

原型链

试想一下,如果用原型A的实例来作为构造函数B的原型会发生什么?
没错,就是实例B拥有了原型A的属性和方法。
因为这涉及到指针操作,所以要注意赋值操作的顺序

只用原型链继承存在的问题

  1. 因为原型A的实例是构造函数B的原型,所以原型A的实例的引用类型属性会被所有实例B所共享(还是那个道理,不想被共享的引用类型不要放原型中)
  2. 无法在不影响所有实例的前提下向超类型(即原型A)的构造函数传递参数

结构构造函数(相当于构造函数代码拼接)

直接看代码吧:

  1. function SuperType(namne){
  2. this.name = name;
  3. }
  4. function SubType(){
  5. //把this传递过去相当于把上面的代码复制了过来
  6. SuperType.call(this, "Nicholas");
  7. //实例属性
  8. this.age = 29;
  9. }

存在的问题

还是构造函数的老问题,方法不共用

组合继承(ES5最常用)

即属性用构造函数继承,方法通过原型链继承

  1. //超类型方法的定义
  2. SuperType.prototype.sayName = function(){
  3. alert(this.name);
  4. }
  5. //子类型继承方法
  6. SubType.prototype = new SuperType();
  7. //子类型定义方法
  8. SubType.prototype.sayAge = alert(this.age);

原型式继承(即对象的浅复制)

思想是用一个实例函数作为另一个对象的原型。
ES5中提供了方法Object.create()来实现,第一个参数为用作原型的实例对象,第二个参数(可忽略)为额外的属性值,需要同时写上属性描述符;

寄生式继承

即用函数把定义自己的方法这一操作打包起来,同样方法不能共享。

寄生组合式继承(最优选择)

细心的话可以发现,上述的组合继承中在原型中继承的属性是多余的
实现方法如下:

  1. function inheritPrototype(subType, SuperType){
  2. //浅复制超类型的原型(即只含有方法)
  3. var prototype = Object.create(SuperType.prototype);
  4. //将刚才浅复制的原型的constructor指向subType
  5. prototype.constructor = subType;
  6. //subType的prototype指向上面复制的原型
  7. subType.prototype = prototype;
  8. }

创建对象

var o = new Object();var o = {};等价。

在对象上部署方法方法

  1. 部署在Object对象本身。
  2. 部署在Object.prototype对象(原型对象,其方法被所有实例对象共享。)

Object()

Object()本身也是一个工具方法,作用是:将任意值转为对象。
该方法常用于保证某个值一定是对象。

如果参数是原始类型的值,Object方法返回对应的包装对象的实例(参见《原始类型的包装对象》一节)。

Object对象的静态方法

Object对象的静态方法指的是部署在Object这个对象上的方法(非实例对象)。

遍历对象的属性

  • Object.keys()
  • Object.getOwnPropertyNames()

Object.keys()更常用,用于返回可枚举的属性。
Object.getOwnPropertyNames()返回可枚举和不可枚举的属性(例如数组中的.length

对象属性模型的相关方法

  • Object.getOwnPropertyDescriptor():获取某个属性的attributes对象。
  • Object.defineProperty():通过attributes对象,定义某个属性。
  • Object.defineProperties():通过attributes对象,定义多个属性。
  • Object.getOwnPropertyNames():返回直接定义在某个对象上面的全部属性的名称。

控制对象状态的方法

  • Object.preventExtensions():防止对象扩展。
  • Object.isExtensible():判断对象是否可扩展。
  • Object.seal():禁止对象配置。
  • Object.isSealed():判断一个对象是否可配置。
  • Object.freeze():冻结一个对象。
  • Object.isFrozen():判断一个对象是否被冻结。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注