最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript 原型篇

    正文概述 掘金(_李国庆)   2020-12-26   232

    前言

    对于每一位前端开发人员来说 JavaScript 语言本身是相当重要的,掌握好这一门语言就是我们的立足之本。但是对于语言本身的一些难点,可能我们每次都掌握的不扎实,不能把他挖深了。这一节就针对原型与原型链这一部分内容进行深入的彻底学习,一劳永逸。

    本文我会尽量使用通俗易懂的方式阐述,并且加杂着图片辅助大家理解。我会从原型本身的定义讲起直到前端社区中对原型与原型链的经典使用,由潜入深的学习这一块知识。将知识体系化,实践化。相信通过这样,大家都可以一次性的学会原型与原型链

    为什么需要原型

    在传统的面向对象语言比如 java 中,使用 class 关键字定义一个类,我们可以创建一个类的实例对象。

    但是在我们熟悉的 javascript 这门语言中其实并没有这一概念,它是使用了一种不常用的方式来实现面向对象这一特性,这种方式就是原型

    所以原型是 javascript 的作者在设计这门语言的时候为了支持面向对象的选择。

    prototype(原型对象)是什么

    javascript 规定,每一个函数都有一个 prototype 属性,指向另一个对象(原型对象)

    这个原型对象上的所有属性和方法都会被它的构造器函数的实例继承。

    我们来创建一个函数,并且打印一下它的 prototype 属性一探究竟:

    function Person() {
      
    }
    console.log(Person.prototype);
    

    浏览器中执行这段代码,返回结果为:

    JavaScript 原型篇

    首先这确实是一个对象,它有两个属性,我们先看 constructor 这一属性。它的值是一个函数,看起来就是 Person 本身,来验证一下:

    console.log(Person.prototype.constructor === Person); // true
    

    果然是它本身。所以说 Person 这一函数其实就是它的原型对象的构造器函数

    我们再来看这个构造函数的实例对象会不会继承原型对象上的方法。 首先我会给 Person 的原型对象添加一个 name 属性和 getName 方法,然后访问它的实例对象上有没有继承对应的属性和方法:

    function Person() {
    
    };
    Person.prototype.name = 'Person 原型对象上的 name 属性';
    Person.prototype.getName = function() {
      console.log(this.name);
    };
    const person = new Person();
    console.log(person.name);
    person.getName();
    

    同样在浏览器中看执行结果:

    JavaScript 原型篇

    可以看到得到了预期的结果,这样也就解释了什么是原型对象并且验证了继承这一特性

    __proto__

    上面我们已经知道了每一个函数都有一个原型对象,并且构造函数的实例对象会继承这个原型对象的属性和方法

    那么这个实例对象就肯定跟构造函数的原型对象之间存在着某种联系。这一个联系表现在实例对象的 __proto__ 这一属性上。

    也就是说实例对象__proto__ 这一属性的属性值指向它的构造函数的原型对象。我们再来加以验证:

    function Person() {
    
    };
    const person = new Person();
    console.log(person.__proto__ === Person.prototype);
    

    可以看到浏览器的执行结果为 true

    JavaScript 原型篇

    在讲解原型链之前,我们先来做一个铺垫:

    其实,对于每一个对象来说,它都有 __proto__ 这一属性,注意这里的对象是泛指的。也就是说,对于数组正则日期函数等对象都具有这一属性。因为函数其实也是一个对象,在 javascript 中,万物皆是对象。

    我们来验证一下函数具有这一属性:

    function Person() {
    
    };
    console.log(Person.__proto__);
    

    可以在浏览器中看到:

    JavaScript 原型篇

    我们先不管这是什么,起码我们已经验证了函数普通对象等都具有 __proto__ 这一属性,这对后面介绍原型链具有很大帮助。

    原型链

    了解原型之后,也就到了一个最重要的环节——原型链

    javascript 语言在原型的基础上通过链表这一数据结构将原型成链成环。搞清楚这一特性后,可以对语言有更深层次的认知,使用层面上也会让你变得游刃有余。

    先来将之前所述中重要的几点做一个总结:

    • 每一个函数都有一个 prototype 属性,指向它的原型对象。
    • 这个原型对象上的所有属性和方法都会被它的构造器函数的实例对象继承。
    • 任一对象的 __proto__ 属性都指向它的构造器函数的原型对象。

    数组的原型链

    我们先来拿数组举个栗子。数组也是对象类型的数据,所以每一个数组都具有 __proto__ 属性,我们来验证一下上面的第三条:

    const arr = [1, 2, 3, 4];
    console.log(arr.__proto__ === Array.prototype); // true
    

    其实,这是因为上面的数组声明方式其实相当于:

    const arr = new Array(1, 2, 3, 4);
    

    所以,arr 是构造函数 Array 的一个实例对象并且我们已经验证了第三点。即它的 __proto__ 属性指向 Array 的原型对象。

    那么我们就可以根据第二点得出这一数组会继承 Array 的原型对象上的属性和方法,我们先来看一下 Array.prototype 上面究竟有什么属性和方法。

    console.log(Array.prototype);
    

    可以看到:

    JavaScript 原型篇

    这里只是截取了部分属性方法,我们看到了熟悉的 concatfilterindexOf 等等方法,并且看到了一个属性 length

    看到这里大家就懂了为什么我们只是声明了一个数组,为什么它上面就有那么多的属性和方法呢。其实这都归功于 javascript 本身的原型与原型链机制。

    到这里,我还迟迟没有推出原型链这一特性,接下来,就让我们沿着数组的原型链去一探究竟。

    我们知道 arr.__proto__ 其实就是 Array.prototype 。那么 arr.__proto__.__proto__ 就应该是 Array.prototype.__proto__

    我们来分析一下 Array.prototype.__proto__

    1. 首先 Array.prototype 可以看做数组的原型对象,那么它就是一个拥有键值对的对象。
    2. 又有一个简单对象的 __proto__ 属性指向 Object.prototype
    3. 可以猜测 Array.prototype.__proto__ 就是 Object.prototype

    验证一下:

    Object.prototype === Array.prototype.__proto__; // true
    

    由于继承的传递性,可以得到每一个数组也都拥有 Obejct.prototype 这一原型对象上的属性和方法。

    JavaScript 原型篇

    我想当你看到 toStringvalueOf 这些方法的时候你就已经一目了然了,原来一个对象上的这些方法都继承来自 Obejct.prototype,不过有些原型对象上的这些方法被改写了而已。

    当我们都以为一切都已经到头了的时候,我们发现 Object.prototype 这一原型对象也具有它的 __proto__ 属性,这也应证了我说的每一个对象都具有这个属性:

    JavaScript 原型篇

    展开看一下:

    JavaScript 原型篇

    可以看到它上面有四个简单的属性:argumentscallerlengthname,熟悉函数的同学肯定知道这不是每一个函数上面应该具有的属性吗。

    这究竟又是怎么回事呢?我们在讨论对象,怎么又跟函数扯上了关系。之前已经说了,所有的函数本质上都是对象,那么 Object 这一构造函数也不例外,它也是通过 new Function 出来的。所以有 Object.__proto__ === Function.prototype

    JavaScript 原型篇

    讨论到这里后,我们回到 Person 的例子,Person 这一构造函数也具有原型对象,这一原型对象也是 Object 构造函数的实例对象。

    所以有 Person.prototype.__proto__ === Object.prototype 这一结论是成立的。

    那么重点来了,Function 也是一个对象,它和 Object 之前有什么关系呢?

    首先排除掉 Function 是通过 new Object 出来的这种可能性。因为 Person.prototype 是通过 new Object 出来的,又有Person.__proto__ === Function.prototype

    那么 Function.__proto__ 到底指向谁呢?其实这里就是 javascript 语言本身的原型闭环。直接上答案:Function.__proto__ === Function.prototype。可以看到这时非常特殊的。

    距离原型链的顶端 null 就只有两部之遥了,加把劲,我们就要彻底搞清了。

    Function 的原型对象也是一个对象,所以这个原型对象也是 Object 的实例,就有:Function.prototype.__proto__ === Object.prototype

    那么再往上找,Object 的原型对象的 __proto__ 就指向 null 了,因为它不可能是 new Object 获得的。这样,整个原型与原型链的机制我们就都摸清楚了。

    我将上述讲解到的内容做出以下图解,相信你可以理清楚其中所有的脉络:

    JavaScript 原型篇

    instanceof

    学完整个原型链后其实我们也学会了一个运算符 instanceof

    instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,它的用法很简单,大家可以自行学习,这里继续本文的主题。

    趁热打铁,我们来探索一下原型与原型链在社区中的优秀用例吧。

    社区中的使用

    关于原型与原型链在社区中的使用很多,这里我挑选出了两个大家耳熟能详的库和框架中的案例,jQueryVue

    先让我们来看看在 jQuery 中是怎么巧妙地使用原型机制的。

    jQuery

    jQuery 相信我们每一个人都用过,最早也是它将原型链整个机制发扬光大的。

    不知道你有没有发现这样一个现象,就是当我们通过 $() 创建出来一个 jQuery 的实例对象的时候,浏览器的控制台中显示的是这样子的:

    JavaScript 原型篇

    它其实是 jQuery.fn.init 这一构造函数的实例对象,是不是很纳闷,让我们带着疑惑走进 jQuery 的源码进而探究其对原型机制的使用。

    我们这里仅仅展示一个简单的架子,根据上面的结果,有下面的推测:

    (function(window) {
      var jQuery = function(selector) {
        return new jQuery.fn.init(selector);  
      };
    
      jQuery.fn = jQuery.prototype = {
        css() {},
        html() {},
        each() {}
      };
    
      jQuery.fn.init = function(selector) {
        
      };
    
      window.jQuery = window.$ = jQuery;
    })(window);
    

    相信我们都可以看懂这几行代码吧,这里简单的解释一下:

    • 首先整个代码放入一个自执行函数中,避免污染全局变量。
    • 内部声明一个名为 jQuery 的函数,最后像全局 window 上挂载 jQuery$ 供外部使用。
    • 由浏览器中看到的返回的实例对象其实是 jQuery.fn.init 得到 jQuery 内部其实是 new jQuery.fn.init() 来创建实例对象。这里 jQuery.fn 其实是 jQuery.prototype 的一个引用。
    • 之后在 jQuery 的原型对象上添加了很多供外部使用的方法,例如 csshtml等等。

    解释完后疑问就随之产生了,为什么我们给 jQuery 的原型对象上添加的方法,jQuery.fn.init 的实例对象却可以访问到呢?

    正当我们百思不得其解的时候,下面这行代码给了我们答案:

    jQuery.fn.init.prototype = jQuery.fn;
    

    这行代码是让jQuery.fn.init 的原型对象指向了 jQuery 的原型对象。这样一来 jQuery.fn.init 的实例对象就可以拿到 jQuery 的原型上的方法,这里给出图解:

    JavaScript 原型篇

    这一原型共享的设计可谓是十分的巧妙。

    Vue

    了解了 jQuery 中的共享原型后,我们再来探索一下在 Vue2.x 中的代理原型的设计。

    Vue2.x 的响应式系统中,为了拦截可以改变数组本身的 7 个操作:pushpopunshiftshiftsplicereversesort。设计出了代理原型的模式来进行拦截。

    我们来探究一下其内部是怎么做的:

    const arrayProto = Array.prototype;
    const proto = Object.create(arrayProto);
    [
      'push',
      'pop',
      'unshift',
      'shift',
      'splice',
      'reverse',
      'sort'
    ].forEach(method => {
      proto[method] = function(...args) {
        console.log(`拦截到了${method}方法的执行`);
        arrayProto[method].call(this, ...args);
      }
    });
    
    const arr = [1, 2, 3];
    arr.__proto__ = proto;
    arr.push(4);
    console.log(arr);
    

    我们来解析一下以上代码:

    • 首先 arrayProto 拿到数组原型对象的引用。
    • 创建一个 proto 的对象,继承自数组的原型对象,此时 proto 上继承了所有数组原型对象的属性和方法。
    • 接着遍历准备好的包含七个方法名称的数组,依次重写 proto 上的这些方法。
    • proto 对象上对应的这七个方法分别进行了一次拦截操作,并且内部其实继续使用 call 绑定 this 指向调用了数组原型上的对应的方法。
    • 接着我们创建了一个数组,修改其 __proto__ 属性指向新创建的对象,之后调用 push 方法。

    可以在浏览器中看到:

    JavaScript 原型篇

    我们对对应的方法进行了拦截,并且没有破坏数组原来的方法的作用(这里指添加一个元素)。

    这就是 Vue2.x 中对原型的高级使用。

    使用原型

    在了解了优秀的类库或者框架中对原型机制的使用后,我们在日常代码中可以怎么使用这一机制来优化我们的代码呢?

    这里我将给大家举个例子,例如我们要实现一个数组的去重方法,我们可以这样设计代码:

    Array.prototype.unique = function() {
      return Array.from(new Set(this));
    };
    

    之后你只需要这样使用:

    const arr = [1, 2, 3, 1, 2];
    console.log(arr.unique()); // [1, 2, 3]
    

    当然你可以将你的 unique 设计的更为强大,例如可以添加一个回调函数等。

    但是有一个需要注意的是,如果你想要在项目中使用此类方法,一定要确保这以后不会称为 javascript 的语言规范,不然对于一个长期使用的项目来说会是一个很大的麻烦。例如,上面的 unique 函数以后成为了语言规范(这里只是假设),在你的项目中后面来的新人在维护项目的时候使用了这个方法。因为他不知道你已经修改了原型上的这个方法,会对他们的使用造成很大的困惑。所以我们在项目中使用的时候要注意命名,尽量避免这类问题。

    总结

    有关 javascript 语言中的原型与原型链机制这里就介绍完毕了,相信弄清楚上面的知识后,有关这部分的问题对你来说再也不会是问题了,你也不需要再回过头来一次又一次的似懂非懂的学习。

    将一个知识点从使用到原理解读、底层实现、社区如何使用、最后到自己实战中使用经历这些步骤后肯定会对它不再畏惧。最后将所有的知识点总结起来,关联起来就可以形成一个完整的知识体系。


    起源地下载网 » JavaScript 原型篇

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元