整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

这篇文章,让你了解 JavaScript 中的原型(

这篇文章,让你了解 JavaScript 中的原型(基础篇-图文)

avaScript - Prototype

JavaScript是一门动态语言, 你可以在任何时候向对象上添加属性,如下

function Student() {
 this.name='LeBron James';
 this.gender='Male';
 }
 var studObj1=new Student();
 studObj1.age=15;
 alert(studObj1.age); // 15
 var studObj2=new Student();
 alert(studObj2.age); // undefined

正如上面的实例, age 属性附加在 studObj1 实例上. 然而 studObj2 实例没有这个属性, 因为 age 属性只在 studObj1 实例上定义了.

那么, 如果想在后期添加一个属性且能被所有的实例所共享, 该怎么办? 答案这就今天主角 Prototype.

Prototype 是一个对象, 默认情况下与JavaScript中的任何一个函数或对象有关, 只是唯一区别在于函数的prototype 属性是可访问和可修改的,而对象的prototype属性是不可见的.

默认情况下任何一个函数包含 Prototype 对象, 如下图:

prototype 对象是一种特殊类型的可枚举对象, 可以将需要附加属添加到其上,这些属性将在其构造函数的所有实例之间共享。

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

因此, 把上面的示例中使用函数的 prototype 来添加属性,以便于所有对象中都可以访问到, 如下:

function Student() {
 this.name='LeBron James';
 this.gender='M';
 }
 Student.prototype.age=15;
 var studObj1=new Student();
 alert(studObj1.age); // 15
 var studObj2=new Student();
 alert(studObj2.age); // 15

使用 字面量 或 通过 new关键字和构造函数 的方式创建的每一个对象都包含 __proto__ 属性, 该属性指向创建此对象的函数的 原型对象.

你可以在谷歌和火狐开发者调试工具中查看该属性(__proto__) , 根据下面的示例:

function Student() {
 this.name='LeBron James';
 this.gender='M';
 }
 var studObj=new Student();
 console.log(Student.prototype); // object
 console.log(studObj.prototype); // undefined
 console.log(studObj.__proto__); // object
 console.log(typeof Student.prototype); // object
 console.log(typeof studObj.__proto__); // object
 console.log(Student.prototype===studObj.__proto__ ); // true

正如上面例子看到, 函数通过 [[函数名称]].prototype 方式访问到原型对象. 但是, 对象(实例)并没有暴露出 prototype 属性,而是使用 __proto__ 来访问它.

Object 对象的原型

前面提及到, 原型对象在对象中是不可见. 使用 Object.getPrototypeOf(obj) 方法来访问实例的原型对象. (这也是推荐方式, __proto__ 并不是标准属性, 在IE11以下其它浏览器中没有实现).

function Student() {
 this.name='LeBron James';
 this.gender='M';
 }
 var studObj=new Student();
 Student.prototype.sayHi=function(){
 alert("Hi");
 };
 var studObj1=new Student();
 var proto=Object.getPrototypeOf(studObj1); 
 // returns Student's prototype object
 
 alert(proto.constructor); 
 // returns Student function 

Object 原型对象包含如下 属性 和 方法

属性描述constructor返回创建该实例的构造函数__proto__指向创建该实例的构造函数的原型对象.方法描述hasOwnProperty()返回一个布尔值,指示对象是否包含指定的属性作为该对象的直接属性,而不是通过原型链继承。isPrototypeOf()返回一个布尔值,指示指定的对象是否位于调用此方法的对象的原型链中。propertyIsEnumerable()返回一个布尔值,该布尔值指示指定的属性是否可枚举。toLocaleString()返回本地格式的字符串.toString()返回对象字符串形式.valueOf()返回指定对象的原始值.

Chrome 和 Firfox 将对象的原型表示为 __proto__, 而内部引用为 [[Prototype]]. IE不支持,只有IE11包含它.

修改原型

如上所述, 每个对象都能链接到函数的原型对象. 如果您更改了函数的原型, 则只有新对象将链接到更改后的原型. 所有其他现有对象仍然链接到旧的函数原型. 下面实例来演示这个场景:

function Student() {
 this.name='LeBron James';
 this.gender='M';
 }
 Student.prototype.age=15;
 var studObj1=new Student();
 alert('studObj1.age=' + studObj1.age); // 15
 var studObj2=new Student();
 alert('studObj2.age=' + studObj2.age); // 15
 Student.prototype={ age : 20 };
 var studObj3=new Student();
 alert('studObj3.age=' + studObj3.age); // 20
 alert('studObj1.age=' + studObj1.age); // 15
 alert('studObj2.age=' + studObj2.age); // 15

使用原型

原型对象被JavaScript引擎用来做两件事:

  • 查找对象的属性和方法在JavaScript中实现继承
function Student() {
 this.name='LeBron James';
 this.gender='M';
 }
 Student.prototype.sayHi=function(){
 alert("Hi");
 };
 var studObj=new Student();
 studObj.toString();

在上面的示例, toString() 方法在 Student 中没有定义, 那么它是如何以及从哪里找到 toString() 的呢?

在这里,原型出现了. 首先, JavaScript 引擎检查 studObj 是否存在 toString 方法?. 如果没有找到,那么它使用 studObj 的 __proto__ 链接指向 Student函数 的 原型对象. 如果它仍然无法找到它那么它会在往上层并检查 Object 函数的原型对象,因为所有对象都是从 JavaScript 中的 Object 派生的,并查找 toString() 方法. 因此, 它在Object函数的原型对象中找到 toString()方法,因此我们可以调用 studObj.toString().

查找方式,如下图所示

上述就是原型基本知识点以及应用.

原作者姓名:任重道远

原出处:SenmentFault

原文链接:人类身份验证 - SegmentFault

接修改原型可能会影响所有继承自 HTMLElement 的元素,因此需要谨慎操作。

以下是一个简单的示例,展示如何在 HTMLElement 的原型上添加一个新的方法:



// 添加一个新的方法到 HTMLElement 的原型
HTMLElement.prototype.sayHello=function() {
console.log(`Hello from ${this.tagName}`);
};
// 使用新的方法
document.body.sayHello(); // 输出: Hello from BODY

不过,更推荐的方式是使用 自定义元素 来扩展功能,这样可以避免对全局原型的修改,减少潜在的冲突和问题。 通过继承 HTMLElement 来创建自定义元素:

理解原型链,绕不开constructor、prototype、__proto__这几个核心的知识点,它们的关系如下:

上面的图是一个最简单的原型链,先有一个直观的认识。下面将围绕上面3个点一步步对原型链抽丝剥茧,最后在来总结究竟什么是原型链,自然就清晰了。

2. 课前预习

再正式进入主题之前,先了解几个知识点

2.1 函数对象与普通对象

JavaScript 中,万物皆对象,但对象也是有区别的。分为普通对象和函数对象, Object 、Function 是 JS 自带的函数对象

  • 函数对象
//f1,f2,归根结底都是通过 new Function()的方式进行创建的
function f1(){};//console.log(typeof f1)==function 
var f2=function(){};//同上
var f3=new Function('str','console.log(str)');//同上
  • 普通对象
var o1={}; // console.log(typeof o1)==object 
var o2=new Object();//同上
var o3=new f1();//同上

简单的说,凡是使用 function 关键字或 Function 构造函数创建的对象都是函数对象,其他的都是普通对象

2.2 几个专业术语

  • 显式原型:prototype
  • 隐式原型:__ proto __
  • 原型对象:每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象

3. prototype和contructor

  • prototype指向函数的 原型对象 ,只有函数或者说函数对象才拥有该属性。
  • contructor指向原型对象的构造函数。

可能很多人对什么是原型对象比较迷惑,个人是按照下面的模版记得:

原型对象=构造函数名.prototype

//Person.prototype就是原型对象
function Person() {}
Person.prototype={
   name:  'Zaxlct',
   sayName: function() {
   }
}

3.1 prototype和contructor关系

主要从三个纬度来分析:自定义的构造函数(Person)、Object函数、Function函数

// 可以思考一下打印结果
function Person() {}
console.log(Person.prototype)//[object Object]
console.log(Person.prototype.constructor)//function Person() {}

3.2 prototype创建时机

在定义函数时自动添加的, 默认值是一个空Object对象

//定义构造函数
function Fn() {   // 内部语句: this.prototype={}
}

3.3 prototype作用

  • 通过给 Person.prototype 设置属性和方法之后,Person 的实例都会继承相应的属性和方法,所有的实列例共享一份,不会开辟新的空间。
  • prototype用来实现基于原型的继承与属性的共享

4. proto

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。

由于js中是没有类的概念,而为了实现继承,通过 __ proto __ 将对象和原型联系起来组成原型链,就可以让对象访问到不属于自己的属性。

4.1 函数和对象的关系

所有函数都是Function的实例(包含Function),所有构造器都继承了Function.prototype的属性及方法。

4.2 原型对象间的关系

所有的原型对象__proto__最终指向了Object.prototype(除了Object.prototype的__proto__之外)。

js原型链最终指向的是Object原型对象

4.3 proto创建时机

对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值

function Fn() {  } 
// 内部语句: this.__proto__=Fn.prototype
var fn=new Fn()  

4.4 总结

  • 当你new一个构造函数的时候,创建一个函数实例,那么 『 函数实例.__ proto __===该构造函数.prototype 』
  • 所有的函数都是由Function构造出来的,那么 『被构造出来的其他函数.__ proto __===Function.prototype 』
  • 所有的构造函数的原型对象都是由Object构造出来的,那么 『所有的构造函数.prototype.__ proto __===Object.prototype 』

5. 完整原型链结构图

  • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
  • 这样通过__proto__属性就形成了一个链的结构---->原型链
  • 当查找对象内部的属性方法时, js引擎自动沿着这个原型链查找
  • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

6. 原型链的查找和内存表现

function Person() {
    this.test1=function () {
      console.log('test1()')
    }
  }
  console.log(Person.prototype)
  Person.prototype.test2=function () {
    console.log('test2()')
  }
  Object.prototype.test3=function () {
    console.log('test3()')
  }
  const  personObj=new Person()
  personObj.test1()//test1
  personObj.test2()//test2
  personObj.test3()//test3
  

对应到内存中的原型链如下图:

7.总结

  • 显式原型与隐式原型的关系函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值原型对象即为当前实例对象的父对象
  • 原型链的作用避免相同函数重复声明,减少空间占用,节省内存实现继承构造函数的所有实例都可以访问构造函数原型中的方法和属性