美文网首页
JavaScript对象总结(面向对象、原型链、继承)

JavaScript对象总结(面向对象、原型链、继承)

作者: 花泽冒菜 | 来源:发表于2018-10-04 18:36 被阅读0次

JavaScript数据类型分为基本类型和引用类型,对象则是某个特定引用类型(如原生引用类型:Object、Array、Date、RegExp、Function以及三个基本包装类型Boolean、Number、String)的实例。
对象是一组无序键值对的集合,即key和value的集合。
按照表现形式的不同,对象包含的内容可以分为属性和方法。
方法本质上也是属性的一种,所以有时候提到对象属性时也包含了方法。

对象创建的方式

单个创建对象
  • 使用new操作符后跟构造函数
var person = new Object();
person.name = 'Lily';
person.age = 20;
  • 对象字面量表示法(常用)
var persong = {
  name: 'Lily',
  age: 20
};
批量创建对象
  1. 工厂模式
    将创建对象的过程封装在一个函数中,返回一个对象,缺点在于无法判断对象的类型。
function creatP(name, age) {
  var o = new Object();
  person.name = 'Lily';
  person.age = 20;
  return o;
}
var p1 = creatP('Tom', 21);
p1 instanceof creatP;  // false
  1. 构造函数模式
    将属性值直接赋值给this,用new操作符实例化,可以判断对象的类型,缺点在于会创建很多执行相同任务的方法(函数)。
function Person(name, age) {
  this.name = 'Lily';
  this.age = 20;
  this.sayName = function() {
    console.log(this.name);
  }
}
var p1 = new Person('Tom', 21);
p1 instanceof Person;  // true
p1 instanceof Object;  // true
Person instanceof Object;  // true

var p2 = new Person('Jerry', 20);
p1.sayName === p2.sayName;  // false

构造函数既可以当做构造函数使用,也可以当做普通函数使用。
所有对象都是Object的实例。

  1. 原型模式
    和构造函数的区别在于,不再在构造函数中定义属性和方法,而是将属性和方法直接添加到原型对象中。
function Person() {
}
Person.prototype.name = 'Lily';
Person.prototype.age = 20;
Person.prototype.sayName = function() {
  console.log(this.name);
}
var p1 = new Person();
var p2 = new Person();
p1.sayName === p2.sayName;  // true

每个函数都有一个prototype属性,指向原型对象,这个对象包含了实例对象要共享的所有属性和方法。
原型对象有一个constructor属性,指向prototype属性所在的函数。
即:
构造函数:Person
原型对象:Person.prototype
他们具有以下关系:
Person === Person.prototype.constructor

实例对象的内部也包含一个指向原型对象的指针,在ES5中,这个指针被定义为[[Prototype]],很遗憾,JavaScript中没有标准的方式访问[[Prototype]]。
但是有一个非标准的方法可以访问:Firefox、Safari、Chrome浏览器中为每个实例对象定义了一个__proto__属性,指向原型对象。

简而言之,构造函数、实例对象、原型对象三者之间的关系可以表示为:


网图,侵删

原型对象还包含一个方法isPrototypeOf()用来判断一个对象是不是这个原型对象的实例。
Person.prototype.isPrototypeOf(p1); // true
Object.getPrototypeOf()方法可以用来访问实例的原型对象。
Object.getPrototypeOf(p1) === Person.prototype; // true

实例对象拥有原型对象的所有属性和方法,实例对象自身的同名属性会覆盖原型对象的属性。
实例对象无法更改原型对象的属性。
实例对象的hasOwnProperty()方法可以判断一个属性是否属于自身。
p1.hasOwnProperty('name'); // false
in操作符可以判断对象是否有某个属性(无论自身属性还是原型对象上的属性)
'name' in p1; // true;

遍历对象的属性
- for...in 遍历对象的可枚举属性(key)包含实例和原型中的属性

for (prop in p1) {
  // ...
}

- Object.keys() 遍历对象的可枚举属性,不包括原型的属性
Object.keys(p1); // name, age, sayName
- Object.getOwnPropertyNames() 遍历对象的所有属性,不包括原型的属性
Object.getOwnPropertyNames(Person.prototype); // name, age, sayName, constructor

原型模式的缺点在于,如果原型对象的某个属性值为引用类型,那么所有的实例的该属性都会指向同一个引用类型,对这个引用类型所做的修改将会应用到所有的实例上。

  1. 组合使用构造函数模式和原型模式(推荐)
    结合构造函数模式和原型模式的优点,既能保证实例共用方法,又能保证不共用引用类型,同时初始化实例对象时还能传参。
    即:将值为引用类型的属性写在构造函数中,将方法写在原型对象中。
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.friends = ['Lucy', 'Jack'];
}
Person.prototype = {
  constructor: Person,  // 这种写法相当于重写了原型对象,因此要将consructor指回Person,但此时constructor属性变为可枚举属性了
  sayName: function() {
    console.log(this.name);
  }
};
var p1 = new Person('Tom', 21);
var p2 = new Person('Jerry', 20);
p1.friends.push('Van');
p1.friends === p2.friends;  // false
p1.sayName === p2.sayName;  // true

实例对象的属性仅包含构造函数中定义的属性;原型对象中的属性仅包含直接对原型对象添加的属性。

  1. 动态原型模式
  2. 寄生构造函数模式
  3. 稳妥构造函数模式

继承

原型链继承

原型链是实现继承的主要方法。
什么是原型链?每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针;如果让原型对象A等于实例B,此时原型对象A将包含一个指向原型对象B的指针,原型对象B又包含一个指向构造函数B的指针;假如再让原型对象B等于实例C,上述关系依然成立,如此层层递进,就形成了一条实例与原型的链条。

需要注意,如果要给原型对象A添加属性,一定要在 原型对象A=实例B 这一步之后操作,因为这一步相当于重写了原型对象A,如果在此之前为原型对象A添加属性,将会无效。
constructor属性也会重写,会指向构造函数B而非构造函数A;如果原型对象B也被重写了,那么原型对象A也会继承新的原型对象B继承的原型对象C的constructor属性,指向构造函数C。

原型链继承方式的问题是什么呢?已经知道,原型对象A等于实例B,如果实例B中包含引用类型,那么原型对象A的所有实例对象都会共享上述引用类型,其中一个实例如果修改了引用类型,那么其他实例都会受到影响。

改进方法是在构造函数A中用call/apply调用构造函数B,获得B中定义的属性,依旧让原型对象A等于实例B,并且再将constructor属性指向构造函数A。

function B(name) {
  this.name = name;
  this.friends = ['Jason', 'Jack'];
}
B.prototype.sayName = function() {
  console.log(this.name);
}

function A(name, age) {
  B.call(this, name);
  this.age = age;
}
A.prototype = new B();
A.prototype.constructor = A;
A.prototype.sayAge = function() {
 console.log(this.age);
}

var a = new A('Abby', 20);
a.friends.push('Jimmy');
a.friends;  // ["Jason", "Jack", "Jimmy"]

var b = new B('Baby', 23);
b.friends;  // ["Jason", "Jack"]
原型式继承

如果只想让一个对象与另一个对象保持类似,可以使用Object.create()方法实现继承。
Object.create()接受两个参数,参数1是想要(浅)复制的原型对象,参数2(可选)是为新对象定义额外属性的对象。

var a = {
  name: 'Abby',
  friends: ['Jack', 'Jason']
}
var b = Object.create(a, {
  name: {
    value: 'Baby'
  }
})
b.name;  // 'Baby'
b.friends.push('Jimmy');
a.friends;  // ["Jack", "Jason", "Jimmy"]

注意第二个参数的格式。

访问对象

  • 点操作符(推荐)
    console.log(person.name);
  • 方括号语法
    console.log(person['name']);

检测对象类型

  • 使用对象的constructor属性
var arr = [];
arr.constructor === Array;  // true
  • instanceof操作符(推荐)
var arr = [];
arr instanceof Array;  // true

实际上instanceof检测的是一个构造函数是否出现在实例的原型链中。
isPrototypeOf()则是检测一个原型对象是否出现在实例的原型链中:

var arr = [];
Array.prototype.isPrototypeOf(arr);  // true

相关文章

网友评论

      本文标题:JavaScript对象总结(面向对象、原型链、继承)

      本文链接:https://www.haomeiwen.com/subject/kwrzoftx.html