- 要理解js面向对象,首先得了解原型,对象,构造函数之间的关系,以下面示例为例
function Person(name) { //构造函数
this.name = name;
}
Person.prototype.printName = function () { //原型对象
alert(this.name);
}
var person1 = new Person('Byron'); //实例化对象
console.log(person1.__proto__); //Person
console.log(person1.constructor); //自己试试看会是什么吧
console.log(Person.prototype); //指向原型对象Person
var person2 = new Person('Frank');
- 构造函数:用来在创建对象时初始化对象。特点:构造函数名一般为大写字母开头;与new运算符一起使用来实例化对象。
- 原型:构造函数在创建的过程中,系统自动创建出来与构造函数相关联的一个空的对象。可以由构造函数.prototype来访问到。
- 原型:构造函数在创建的过程中,系统自动创建出来与构造函数相关联的一个空的对象。可以由构造函数.prototype来访问到。
- 在实例化对象p的过程中,系统就自动创建出了构造函数的原型,即Person.prototype.
- 注意:每个对象的__proto__属性指向自身构造函数的prototype;
- constructor属性是原型对象的属性,指向这个原型对象所对应的构造函数。
- 在实例化对象p的过程中,系统就自动创建出了构造函数的原型,即Person.prototype(每个对象的__proto__属性指向自身构造函数的prototype)
- constructor属性是原型对象的属性,指向这个原型对象所对应的构造函数
- 完整原型链图片示意图
new 关键字
// 构造函数表示方法1
// 不需要new生成实例
function Animal (name, energy) {
let animal = Object.create(Animal.prototype)
animal.name = name
animal.energy = energy
return animal
}
// 构造函数表示方法2
// 需要new生成实例
function Animal (name, energy) {
// const this = Object.create(Animal.prototype)
this.name = name
this.energy = energy
// return this
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
-
通常我们使用构造函数2的方法来写一个构造函数,当new 一个关键字时,let this = Object.create(Animal.prototype)、 return this这两句代码,会在引擎会隐式调用,并且创建的对象称为this
-
调用构造函数,忘记new实例防错
// 警告
function Animal (name, energy) {
if (this instanceof Animal === false) {
console.warn('Forgot to call Animal with the new keyword')
}
this.name = name
this.energy = energy
}
// 生成实例
function Animal (name, energy) {
if (this instanceof Animal === false) {
return new Animal(name, energy)
}
this.name = name
this.energy = energy
}
for ... in
function Animal (name, energy) {
this.name = name
this.energy = energy
}
Animal.prototype.eat = function (amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
Animal.prototype.sleep = function (length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
Animal.prototype.play = function (length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
const leo = new Animal('Leo', 7)
for(let key in leo) {
console.log(`Key: ${key}. Value: ${leo[key]}`)
}
- 遍历循环:循环遍历对象本身以及它委托给的原型的所有可枚举属性。因为默认情况下,您添加到函数原型的任何属性都是可枚举的,我们不仅会看到名称和能量,还会看到原型上的所有方法 - 吃,睡,玩等方法。
- 修改只输出原型方法
for(let key in leo) {
if (leo.hasOwnProperty(key)) {
console.log(`Key: ${key}. Value: ${leo[key]}`)
}
}
hasOwnProperty
function Animal (name, energy) {
this.name = name
this.energy = energy
}
Animal.prototype.eat = function (amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
Animal.prototype.sleep = function (length) {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
Animal.prototype.play = function (length) {
console.log(`${this.name} is playing.`)
this.energy -= length
}
const leo = new Animal('Leo', 7)
leo.hasOwnProperty('name') // true
leo.hasOwnProperty('energy') // true
leo.hasOwnProperty('eat') // false
leo.hasOwnProperty('sleep') // false
leo.hasOwnProperty('play') // false
instanceof
function Animal (name, energy) {
this.name = name
this.energy = energy
}
function User () {}
const leo = new Animal('Leo', 7)
leo instanceof Animal // true
leo instanceof User // false
重新创建Object.create
- 它接受一个对象的参数。
Object.create = function (objToDelegateTo) { }
- 它创建一个对象,该对象在失败的查找中委托给参数对象。
Object.create = function (objToDelegateTo) {
function Fn(){}
Fn.prototype = objToDelegateTo
return new Fn()
}
- 在Object.create实现的主体内部,我们将创建一个空函数。然后,我们将该空函数的原型设置为等于参数对象。然后,为了创建一个新对象,我们将使用new关键字调用我们的空函数。如果我们返回新创建的对象
- 注意对于Array、Object等引用类型的数据子实例会共用
var obj = {a: 1, b: ['red', 'blue']}
var objInstance1 = Object.create(obj)
var objInstance2 = Object.create(obj)
objInstance1.b.push('green')
objInstance2.b // ['red', 'blue', 'green']
// 对于基本类型会重新赋值
如:objInstance1.a = 3
console.log(objInstance1) // {a: 3}
- 它返回新创建的对象。
继承实现的方式
- prototype模式实现继承
function Animal() {
this.species = "动物";
}
function Cat(name, color) {
// this 指向Cat
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
// 将Cat的prototype对象指向一个Animal的实例,相当于完全删除了prototype 对象原先的值,然后赋予一个新值
Cat.prototype = new Animal();
// 任何一个prototype对象都有一个constructor属性,指向它的构造函数。
// 如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;
// 加了这一行以后,Cat.prototype.constructor指向Animal。 
// 显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),
// 因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛", "黄色");
// console.log(cat1.species)
console.log(cat1)
- 利用空对象作为中介(寄生组合式继承)
function extend(Child, Parent) {
var F = function(){};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
  Child.uber = Parent.prototype;
}
  
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
- 这种实现继承的方式与上一种比较效率要高些,避免了new Animal两次,其添加不必要的属性。
原型链
- 原型链:每一个对象都有自己的原型对象,原型对象本身也是对象,原型对象也有自己的原型对象,这样就形成了一个链式结构,叫做原型链。
- 以Person举例:这个例子中的p对象的原型链结构图如下:p对象----->Person.prototype------->Object.prototype--------->null
- 对这个实例化对象而言,访问对象的属性,是首先在对象本身去找,如果没有,就会去他的原型对象中找,一直找到原型链的终点;如果是修改对象的属性,如果这个实例化对象中有这个属性,就修改,没有这个属性就添加这个属性。
__proto__属性和prototype属性的区别
- prototype是function对象中专有的属性。
- __proto__是普通对象的隐式属性,在new的时候,会指向prototype所指的对象;
- __ptoto__实际上是某个实体对象的属性,而prototype则是属于构造函数的属性。__ptoto__只能在学习或调试的环境下使用。