深入理解JS原型和继承

在学习JS中的原型,原型链,继承这些知识之前,必须先了解并掌握基础知识:函数和对象的关系

我们一直都知道,函数也是对象的一种,因为通过instanceof就可以判断出来。但是函数和对象的关系并不是简单的包含和被包含的关系,这两者之间的关系还是有点复杂的。接下来我们就来捋一捋。

首先,阐述一点,对象都是通过函数创建的

对于下面这种类型的代码,一般叫做“语法糖” 。

var obj = {a:10,b:20};
var arr = [5, 'x', true];

但是,其实上面这段代码的实质是下面这样的:

//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];

 var obj = new Object();
 obj.a = 10;
 obj.b = 20;

 var arr = new Array();
 arr[0] = 5;
 arr[1] = 'x';
 arr[2] = true;

ObjectArray都是函数,可以自己用typeof函数进行验证。

所以,可以得出:对象都是通过函数创建的

接下来就要进入学习的正文了。

原型prototype

在前文中,我们了解了函数也是一种对象,所以函数也是属性的集合,同时,也可以对函数进行自定义属性。 
每个函数都有一个属性——prototype。这个prototype的属性值是一个对象(属性的集合),默认只有一个叫做constructor的属性,指向这个函数本身。 如下图所示: 

原型prototype

上图中,SuperType是一个函数,右侧的方框就是它的原型

原型既然作为对象(属性的集合),除了constructor外,还可以自定义许多属性,比如下面这样的:

原型prototype

当然了,我们也可以在自己定义的方法的prototype中增加我们自己的属性,比如像下面这样的:

function Fn() { }
    Fn.prototype.name = '张三';
    Fn.prototype.getAge = function () {
       return 12;
};
深入理解JS原型和继承

那么问题来了:函数的prototype到底有何用呢?

在解决这个问题之前,我们还是先来看下另一个让人迷糊的属性:_proto_

“隐式原型”proto

我们先看一段非常常见的代码:

function Fn() { }
   Fn.prototype.name = '张三';
    Fn.prototype.getAge = function () {
       return 12;
};
   var fn = new Fn();
   console.log(fn.name);
   console.log(fn.getAge ());

即,Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。

但是,因为每个对象都有一个隐藏的属性——“_proto_”,这个属性引用了创建这个对象的函数的prototype。即:fn._proto_ === Fn.prototype

那么,这里的_proto_到底是什么呢?

其实,这个__proto__是一个隐藏的属性,javascript不希望开发者用到这个属性值,有的低版本浏览器甚至不支持这个属性值。

var obj = {};
console.log(obj.__proto__);
深入理解JS原型和继承
console.log(Object.prototype);
深入理解JS原型和继承

从上面来看,obj.__proto__Object.prototype的属性一样!为什么呢?

原因是:obj这个对象本质上是被Object函数创建的,因此obj.proto=== Object.prototype。我们可以用一个图来表示。(图片来源于王福明博客) 

深入理解JS原型和继承

即,每个对象都有一个_proto_属性,指向创建该对象的函数的prototype。

说一下自定义函数的prototype:

自定义函数的prototype本质上就是和 var obj = {} 是一样的,都是被Object创建,所以它的__proto__指向的就是Object.prototype

但是,Object.prototype确实一个特例——它的__proto__指向的是null,切记切记!!!(图片来源于王福朋博客) 

深入理解JS原型和继承

另外一个问题:函数也是一种对象,函数也有__proto__吗? 

答:当然也不例外啦!

下面用一段代码和一张图来说明这个问题,看完相信就有个比较直观的理解啦!

function fn(x, y) {
        return x+y;
}
  console.log(fn(10,20));

//以下只是为了演示函数被Function创建的
  var fn1 = new Function("x","y","return x+y;");
  console.log(fn1(5,6));

用图表示就是:(图片来源于王福朋博客)

深入理解JS原型和继承

从上图可以看出:自定义函数Foo.__proto__指向Function.prototypeObject.__proto__指向Function.prototype

但是,为什么有Function.__proto__指向Function.prototype呢?

  其实原因很简单:Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以Function是被自身创建的。所以它的__proto__指向了自身的Prototype

最后一个问题:Function.prototype指向的对象,它的proto是不是也指向Object.prototype?

答案是肯定的。因为Function.prototype指向的对象也是一个普通的被Object创建的对象,所以也遵循基本的规则。如下图:(图片来源于王福朋博客)

深入理解JS原型和继承

说了这么多,我们将上面这些图片整合到一整个图片,便于整体理解,图片如下:(图片来源于王福明博客) 

深入理解JS原型和继承
instanceof

主要是说明下instanceof的判断规则是如何进行的。先看如下代码和图片:(图片来源于王福朋博客)

function fn() {
}
var f1 = new fn();

console.log(f1 instanceof fn);//true
console.log(f1 instanceof Object);//true
深入理解JS原型和继承

instanceof的判断规则为: 
假设instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。

instanceof的判断规则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false

结合这个判断规则,上面的代码和图示相信很容易看懂了。

 原型继承

首先说一下什么是原型链: 访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着_proto_这条链向上找,这就是原型链。

举一个例子说明下吧: 
在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢? 
答案就是:hasOwnProperty这个函数,特别是在for…in…循环中,一定要注意。(图片来源于王福明博客)

深入理解JS原型和继承

但是!!f1本身并没有hasOwnProperty这个方法,那是从哪里来的呢?答案很简单,是从Object.prototype中来的。看下图:(图片来源于王福明博客)

深入理解JS原型和继承

  对象的原型链是沿着__proto__这条线走的,因此在查找f1.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype

由于所有对象的原型链都会找到Object.prototype,因此所有对象都会有Object.prototype的方法。这就是所谓的“继承”。

原创文章,作者:ZERO,如若转载,请注明出处:https://www.edu24.cn/course/javascript/javascript-prototype.html

Like (0)
Donate 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
ZEROZERO
Previous 2019年6月28日
Next 2019年7月2日

相关推荐

  • js数组去重(区分object、“NaN”、NaN)

    数组去重在前端面试中比较常见,今天来复习复习。 对这样一个数组进行去重,我尝试了几种方法,大多数不能对对象去重,或者不能区分true和”true”、NaN和…

    2021年2月23日
    1.2K
  • JavaScript中call、apply及bind的深度解析

    函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this 关键字密切相关,相当一部分人对它们的理解还是比较浅显,所谓js基础…

    2019年8月5日
    1.5K
  • 创建JavaScript对象的六种方式

    第一种:Object 构造函数创建 这行代码创建了 Object 引用类型的一个新实例,然后把实例保存在变量 Person 中。 第二种:使用对象字面量表示法 对象字面量是对象定义…

    2020年6月24日
    1.2K
  • 如何判断一个对象为数组

    使用 instanceof 操作符 原理 instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。 使用 instanceof 判断一个对象…

    2020年7月3日
    1.0K
  • 深入理解JS内存机制

    JS的内存机制在很多前端开发者看来并不是那么重要,但是如果你想深入学习JS,并将它利用好,打造高质量高性能的前端应用,就必须要了解JS的内存机制。对于内存机制理解了以后,一些基本的…

    2019年7月14日
    1.6K
  • JavaScript基础知识八问

    JavaScript是前端开发中非常重要的一门语言,浏览器是他主要运行的地方。JavaScript是一个非常有意思的语言,但是他有很多一些概念,大家经常都会忽略。比如说,原型,闭包…

    2020年12月30日
    890
  • 函数防抖与函数节流

    函数防抖 定义 触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;更直白一点就是:一个需要频繁触发的函数,在规定时间内,只让最后一次生效,…

    2020年7月17日
    1.3K
  • JavaScript 的 this 原理

    有时候会使用一种东西,并不代表你了解它。就像你会写JavaScript代码,能看懂JavaScript代码,但不代表你懂它。 学懂 JavaScript 语言,一个标志就是理解下面…

    2019年8月1日
    1.7K
  • 前端遍历树形数据,返回满足条件的树形数据

    在一次做手机端小程序项目中,有一个机构表单项,需要在页面展示是树形层级结构,但是后端开发人员返回的数据却是一维数组,而且还要在前端做过滤筛选功能。但是在使用的手机端组件库中,却没有…

    2022年11月8日
    338
  • 回调函数散记

    今天被将要入职的公司的开发人员询问了一个项目中遇到的问题,关于函数内访问外部函数的情况。大致现象如下:js文件中有两个同级函数FnA和FnB,想在函数FnA中调用FnB。 一看就是…

    2019年8月16日
    1.4K

发表回复

Please Login to Comment