一眼看穿👀JS原型

原型

原型照样比较重要的,想零丁抽出一章来细说,说到原型,那末什么是原型呢?

在组织函数建立出来的时刻,都有一个prototype(原型)属性,这个属性是一个指针,体系会默许的建立并关联一个对象,这个对象就是原型,原型对象默许是空对象,而这个对象的用处是包含可以由特定范例的一切实例同享的属性和要领。

说白了就是可以在组织函数上挪用prototype属性来指向原型,从而建立谁人对象实例的原型对象

运用原型有什么优点呢?

运用原型的优点是可以让一切对象实例同享它所包含的属性和要领。

转晕了么?是不是是超等乱?🤔又组织函数,又原型,又实例,不忧郁,我一句话点破你
我们一切的组织函数终究都要演变成实例才有意义,由于在组织函数中定义要领没法被一切的实例同享,因而只能找组织函数的上一级,就是原型,在原型上定义的属性和要领可以被一切的实例所同享,这就是对原型对象的性子

看个图你就晓得了,它们三者之间就是三角恋关联👪
《一眼看穿👀JS原型》
很通俗易懂了吧
组织函数.prototype = 原型
原型.constructor = 组织函数
实例对象.constructor = 组织函数(这是由于实例对象在自身找不到constructor属性,那末他会经由过程__proto__去原型中找,经由过程原型搭桥指向了组织函数)
实例对象.__proto__ = 原型

原型是打印显现不出来的,只能经由过程 组织函数.prototype 去示意

下面引见别的两个猎取原型的要领🎁

isPrototypeOf()要领:用于推断这个实例的指针是不是指向这个原型。

Object.getPrototypeOf()要领:猎取实例的原型,这个要领支撑的浏览器有IE9+、Firefox 3.5+、Safari 5+、Opera 12+和Chrome,因而比较引荐经由过程这个要领猎取对象的原型。

假定有个Person组织函数和person对象
Person.prototype.isPrototypeof(person)  // 返回true申明前者是person1的原型
Object.getPrototypeOf(person) === Person.prototype // 猎取person的原型

多个对象实例同享原型所保留的属性和要领的基本原理

每当代码读取某个对象的某个属性时,都邑实行一次搜刮,目的是具有给定名字的属性。首先从对象实例自身最先。假如在实例中找到了具有给定名字的属性,则返回该属性的值;假如没有找到,则继承搜刮指针指向的原型对象,在原型对象中查找具有给定名字的属性。假如在原型对象中找到了这个属性,则返回该属性值。

我们可以接见原型中的值,然则却不能重写原型中的值,假如我们在实例中增加一个属性,而这个属性名和原型中的重名,则这个属性将会屏障(复写)原型中的谁人属性。

function Person() {}

Person.prototype.name = "George"
Person.prototype.sayName = function() {
    console.log(this.name)
}

let person1 = new Person();
let person2 = new Person();
person1.name = "定名最头痛";

// 在这一环节,person1.name会从他实例中找,若实例没找到,则继承搜刮它的原型对象
console.log(person1.name); // 定名最头痛 
console.log(person2.name); // George

在实例对象中增加一属性只会阻挠我们接见原型中的谁人属性,但不会修正谁人属性。纵然将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的衔接

若想完整删除实例属性,可运用delete操作符,从而让我们可以从新接见原型中的属性。

delete 操作符的运用

照旧用上面谁人例子
delete操作符可用于删除对象的属性,不管是实例上的属性,照样在原型上的属性都可以删

delete person1.name    // 删除这个实例的属性
delete Person.prototype.name    // 删除原型上的属性
delete Person.prototype.constructor // 删除constructor属性,如许就没办法指回函数了

hasOwnProperty()要领可用来检测一个属性是存在于实例中,照样存在于原型中。这个要领只在给定属性存在于对象实例时,才返回true,也可以如许邃晓,hasOwnProperty要领用于检测这个属性是不是是对象自身属性。

obj.hasOwnProperty(‘属性名’)

Demo:

function Person(){ 
  this.name = '定名最头痛'
}
var person = new Person()
Person.prototype.age = '18'
console.log(person.hasOwnProperty('name'))  // true
console.log(Person.prototype.hasOwnProperty('age')) // true

in操作符

in操作符有两种用法

①放在for-in轮回中运用,for-in可以返回一切可以经由过程对象接见的、可罗列的(enumerable)属性(可罗列属性可参看
一眼看穿👀JS对象中对象属性部份)个中,既包含存在于实例中的属性,也包含存在于原型中的属性,屏障了一切不可罗列的属性

②零丁运用时,in操作符会在经由过程对象可以接见给定属性时返回true,不管该属性存在于实例中照样原型中。说白了就是,可以经由过程in操作符推断这个对象是不是有这个属性。in操作符会先在实例中寻觅属性,寻觅无果再去原型中寻觅,但不可以逆向查找。

举个🌰(in 零丁运用)

function Person(){}
var person = new Person()
// name存在于实例中
person.name = '定名最头痛'
// age存在于原型中
Person.prototype.age = '18'
console.log('name' in person)  // true
console.log('name' in Person.prototype) // false 不可逆向查找,原型中不能找到实例中属性
console.log('age' in person)  // true
console.log('age' in Person.prototype) // true

我们可以组织一个函数,用于推断一个属性是在原型中照样在对象实例中,同时运用in和hasOwnProperty()要领可以完成这个函数的组织

// 推断一个属性是不是在原型中
function hasPrototypeProperty(object,name) {
    return (name in object) && !object.hasOwnProperty(name)
}

for-in、Object.keys()和Object.getOwnPropertyNames()

for-in和Object.keys都是用于遍历
可罗列对象属性的,他们的重要区分在于

for-in:
遍历对象上一切可罗列的属性,包含原型上的(会在原型上寻觅)

Object.keys():
只遍历自身的可罗列属性,返回一个属性数组(不会在原型上寻觅)

Object.getOwnPropertyNames:猎取自身一切属性,不管它是不是可罗列

function Person() {}
let person = new Person()
// 实例上定义属性
Object.defineProperties(person, {
  name: {
    value: '定名最头痛',
    enumerable: true
  },
  age: {
    value: 18
  }
}) 
// 原型上定义属性
Object.defineProperties(Object.getPrototypeOf(person), {
  year: {
    value: 2018,
    enumerable: true
  },
  job: {
    value: 'programer'
  },
  demo: {
    enumerable: true,
    get() {
      return this.year
    }
  }
})
for(let i in person) {
  console.log(i, person[i]) 
  // name 定名最头痛
  // year 2018
  // demo 2018
}
for(let i in Object.getPrototypeOf(person)){
  console.log(i, Object.getPrototypeOf(person)[i])
  // year 2018
  // demo 2018
}
console.log(Object.keys(person)) // ['name']
console.log(Object.getOwnPropertyNames(person)) // ["name", "age"]
console.log(Object.getOwnPropertyNames(Person)) // ["length", "name", "prototype"]
console.log(Object.getOwnPropertyNames(Person.prototype)) // ["constructor", "year", "job", "demo"]

运用字面量体式格局建立函数原型

上面我们引见了经由过程组织函数体式格局建立函数原型,下面引见一种经由过程字面量的体式格局建立函数原型的要领

function Person() {}
Person.prototype = {
    name: '定名最头痛',
    age: 18,
    sayName: function(){
        console.log(this.name)
    }
}

虽然这类建立体式格局相对于组织函数体式格局在视觉上有更好的封装性,然则这类体式格局建立对象有个破例,那就是constructor属性不再指向Person了,由于每建立一个函数,就会同时建立它的prototype对象,这个对象会自动获得constructor属性。而字面量这类写法,会重写默许的prototype对象,因而constructor属性也就变成了新对象的constructor属性(指向Object组织函数)不再指向Person函数了,此时,只管instanceof操作符还能返回准确的效果,但经由过程constructor却没法肯定对象的范例。

function Person() {}
let person = new Person()
console.log(person instanceof Object) // true
console.log(person instanceof Person) // true
console.log(person.constructor === Person) // false
console.log(person.constructor === Object) // true

若想让字面量体式格局建立原型的constructor从新指向Person,只用刻意为它设置值即可

function Person() {}
Person.prototype = {
    constructor: Person,
    name: '定名最头痛'
    ...
}

如许设置照样有个隐患的,由于我们晓得,直接定义属性的体式格局会致使属性特征默许值为true,如许定义的constructor[[Enumerable]]特征被设置为true,而原生的constructor是不可罗列的,因既想完成constructor指向,又想让[[Enumerable]]特征被设置为true,只能经由过程Object.defineProperty()要领来写constructor

function Person() {}
Person.prototype = {
    name: '定名最头痛',
    ...
}
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
})

原型的动态性

用一句话申邃晓就是原型的变化可以经由过程实例回响反应出来,可以先建立实例,再定义或修正原型上的属性。由于实例与原型之间的衔接是一个指针,而非一个副本,因而可以在原型中找到新的属性并返回保留那边的函数。这类动态性会在多个实例中互相影响,即个中一个实例修正了原型上的内容,也能马上在一切实例中反应出来。这个缘由归结为实例与原型之间的松懈链接关联

只管可以随时在原型中增加属性和要领,而且修正的值可以马上在一切对象实例中反应出来,然则假如从新全部原型对象,效果就不一样了。在挪用组织函数时会为实例增加一个指向最初指向原型的[[prototype]]指针,把原型修正为另一个对象就即是切断了组织函数与最初原型之间的联络,记着:实例中的指针仅指向原型,而不指向组织函数

function Person() {}
let person = new Person();
Person.prototype = {
  constructor: Person,
  name: '定名最头痛',
  sayName: function(){
    console.log(this.name) // 由于重写完后this的指向就变了
  }
}
person.sayName() // error
console.log(person.name) //undefined

《一眼看穿👀JS原型》

也就是说,你可以在原型上定义新属性,然则只管不要重写原型对象,由于这会致使之前实例没法指新定义的原型。
下面经由过程代码申明这一征象

function Person() {}
let person = new Person();
Person.prototype.oldName='George'
console.log(person.oldName) // George

Person.prototype = {
  constructor: Person,
  name: '定名最头痛',
  sayName: function(){
    console.log(this.name)
  }
}
// 重写原型对象后,与实例之间的联络被切断了
console.log(person.name) // undefined
 
// 只能天生新实例才接见新定义的原型
let person1 = new Person();
console.log(person1.name) // 定名最头痛

原生对象的原型

原型形式的重要性不仅体现在建立自定义范例方面,就连一切原生的援用范例,都是采纳这类形式建立的。一切原生援用范例(Object、Array、String等等)都在其组织函数的原型上定义了要领。经由过程原生对象的原型,不仅可以获得一切默许要领的援用,而且也可以定义新要领。

🌰
为原生对象String增加新要领newWay

String.prototype.newWay = function(val) {
   return val
}

原型对象的题目

原型对象省略了为组织函数通报初始化参数这一环节,效果一切实例在默许情况下都将获得雷同的属性值,虽然这会在某些情况下会有些不方便,但还不是原型的最大题目,原型形式的最大题目是由其同享本性所致使的。即原型的动态性,个中一个实例修正了原型上的内容,也能马上在一切实例中反应出来。

同享本性就是双刃剑
假如我们希冀,一切实例同享一个属性值,那原型要领可以很好处理这个题目,然则假如我们希冀每一个实例都有属于本身的属性,把这个属性值定义在原型上就会互相有影响了。

小结

  • 组织函数建立出来,会有一个原型属性prototype,这个属性是一个指针
  • 组织函数,实例,原型三角恋关联图请移至顶部
  • 获得原型的要领
    ①组织函数.prototype
    ②实例.__proto__
    ③Object.getPrototypeOf()要领:该要领用于猎取实例的原型,比较引荐,由于大部份浏览器都支撑
  • isPrototypeOf()要领:用于推断这个实例的指针是不是指向这个原型。
  • delete操作符可以删除对象属性,不管这个属性是在原型上照样实例上
  • hasOwnProperty要领用于检测某一属性是不是是对象自身属性。
  • in操作符有两种运用体式格局
    ①for-in:用于遍历对象可罗列属性,会在原型上搜刮
    ②零丁运用:推断对象中是不是有这个属性,仅限于可罗列属性,返回Boolean范例
  • Object.keys()只遍历自身的可罗列属性,返回一个属性数组
  • Object.getOwnPropertyNames()要领:猎取自身一切属性,不管它是不是可罗列
  • 每建立一个函数,就会同时建立它的prototype对象,这个对象会自动获得constructor属性。
  • 原型形式的最大题目是由其同享本性所致使的

尾声

函数原型的探究之路任重道远,也是JS重点之一。

    原文作者:命名最头痛
    原文地址: https://segmentfault.com/a/1190000016065038
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞