【JavaScript】理解原型与原型链

前言

理解原型和原型链,有助于更好的理解JavaScript中的继承机制。javascript

最近比较有空,因此想写一篇关于原型和原型链的文章,如写得很差请见谅。java

原型对象

不管何时,只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象。在默认状况下,全部的原型对象都会自动得到一个constructor属性,这个属性是一个指向prototype属性所在函数的指针。chrome

function Person(name) {
      this.name = name
    }

Person.prototype.age = 18
Person.prototype.sayName = function() {
  alert(this.name)
}

var person = new Person('张三')
person.sayName() // 张三

上述咱们建立了一个Person构造函数,并建立了一个name实例和原型对象的age属性和sayName方法,接下来new实例对象都拥有这些属性方法浏览器

1

Person构造函数的prototype属性指向原型对象,person实例的[[prototype]]指向原型对象(在chrome浏览器中是__proto__),咱们能够经过isPrototypeOf()方法来确认对象之间是否存在原型关系。ES5新增了一个新方法,Object.PrototypeOf(),这个方法返回[[prototype]]的值函数

alert(Peron.prototype.isPrototypeOf(person))  // true

alert(Object.PrototypeOf(person) == Person.prototype) // true 
alert(Object.PrototypeOf(peroson).name // '张三'

当咱们访问一个实例对象的某个属性时,若是在实例中找到具备给定名字的属性,则返回属性的值;若是没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性,若是在原型对象中找到具备给定名字的属性,则返回该属性的值this

看下面这段代码prototype

function Person() {
}

Person.prototype.name = 'zhangsan';
Person.prototype.age = 18;
Person.prototype.sayName = function() { alert(this.name) };

var person1 = new Person();
var person2 = new Person();

person1.name = 'lisi';
console.log(person1.name); // 'lisi'——来自实例
console.log(person2.name); // 'zhangsan'——来自原型
console.log(person1.hasOwnProperty('name')) // true

delete person1.name; // 'zhangsan'——来自原型
console.log(person1.name);

console.log(person1.hasOwnProperty('name')) // false
console.log(person2.hasOwnProperty('name')) // false

在这个例子中,person1的name被一个新值给屏蔽了。当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。即便将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的链接。不过可使用delete操做符删除实例属性,从而让咱们从新访问原型中的属性。指针

使用hasOwnProperty()方法,能够检测属性是否来自实例,只在给定属性存在于对象实例中时,才会返回truecode

咱们还能够经过定义函数封装一个检测该属性到底存在于对象中,仍是存在于原型中的方法对象

function hasProtoypeProperty(object, name) {
  return !object.hasProperty(name) && (name in object)
}

function Person() {
}

Person.prototype.name = 'zhangsan';
Person.prototype.age = 18;
Person.prototype.sayName = function() { alert(this.name) };

var person = new Person();
console.log(hasProtoypeProperty(person, 'name')); // true

person.name = 'lisi'
console.log(hasProtoypeProperty(person, 'name')); // false

不少同窗可能注意到前面例子每添加一个属性和方法就要敲一遍Person.prototype。为减小没必要要的输入,咱们可使用一个包含全部属性和方法的对象字面量来重写整个原型对象

function Person() {}

Person.prototype = {
  name: 'zhangsan',
  age: 19,
  sayName: function() {
    console.log(this.name)
  }
}

上述代码中有一个问题,Person.prototype中的constructor属性再也不指向Person,此时咱们须要手动将constructor属性指向Person

function Person() {}

Person.prototype = {
  constructor: Person,
  name: 'zhangsan',
  age: 19,
  sayName: function() {
    console.log(this.name)
  }
}

var person = new Person();
for(var val in person) {
  console.log(val); // 'constructor','name','age','sayName'
}

以这种方式重设constructor属性会致使它的[[Enumerable]]特性被设置为true,致使能够经过for-in遍历出来,最好的办法是经过ES5中的Object.defineProperty()方法设置constructor属性

function Person() {}

Person.prototype = {
  name: 'zhangsan',
  age: 19,
  sayName: function() {
    console.log(this.name)
  }
}

Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

原型还有一个特色,是具备动态性,即咱们对原型对象所作的修改都可以当即从实例上反映出来,即便是先建立实例后修改原型对象也是如此

function Person() { }

var person1 = new Person();

Person.prototype = {
  name: 'zhangsan',
  age: 19,
  sayName: function () {
    console.log(this.name)
  }
}

Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

person1.sayName(); // Uncaught TypeError: person.sayName is not a function

var person2 = new Person();
person2.sayName(); // zhangsan

前面提过,调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,上述代码中,重写原型对象切断了现有原型与任何以前已经存在的对象实例之间的联系。实例中的指针指向原型,而不指向构造函数


原型链

在JavaScript中,原型链是实现继承的主要的方法,理解原型链有助于咱们理解JavaScript中的继承机制。

原型链,其基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法,咱们都知道,每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),实例对象都包含一个指向原型对象的内部指针(__proto__),让原型对象等于另外一个类型的实例,此时原型对象将包含一个指向另外一个原型的指针,假如另外一个原型又是另一个类型的实例,如此层层递进,就构成了实例与原型的链条,简称原型链。

在JavaScript中,每一个实例对象都有一个__proto__属性指向它的构造函数的原型对象prototype,原型对象也有一个本身的原型对象__proto__,层层向上直到一个对象的原型对象为null几乎全部JavaScript中的对象都是位于原型链顶端的Object的实例。

function Father() {
  this.name = 'zhangsan';
}

Father.prototype.sayName = function() {
  console.log(this.name);
}

function Son() {
  this.age = 19
}

Son.prototype = new Father();

var xiaoming = new Son();
xiaoming.sayName(); // 'zhangsan'

上述代码中,让Son构造函数的原型指向Father构造函数的实例,此时就实现了JavaScript中最简单的继承原理。让咱们经过下图来更好的理解原型链。

2

上图很清晰的描绘了原型链中的继承机制,对象最顶层Object的原型的原型是指向一个null。注意,在为原型添加方法的代码必定要放在替换原型的语句以后,也不能经过对象字面量建立原型方法,由于这样作就会重写原型链。

function Father() {
  this.name = 'zhangsan';
}

Father.prototype.sayName = function() {
  console.log(this.name);
}

function Son() {
  this.age = 19
}

Son.prototype = new Father();
Son.prototype = {
  sayAge: function() {
    console.log(this.age)
  }
}

var xiaoming = new Son();
xiaoming.sayName(); // Uncaught TypeError: xiaoming.sayName is not a function

使用对象字面量的方式到致使FatherSon之间的关系被切断,所以最后调用sayName()方法就会报错。

总结

以上就是我对原型原型链的理解,原型链虽然强大,但它也存在一些问题,最主要的问题就是包含引用类型值的原型属性会被全部实例共享,本文只是对原型和原型链进行分析,关于继承问题不在本文的讨论范围以内,这是cc的第一篇文章,也算是小白文章,如写得很差请指出,我会及时更正,谢谢你们~

相关文章
相关标签/搜索