关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JavaScript »JavaScript 原型概念深入理解

    JavaScript 原型概念深入理解

    2015-11-03 00:00:00 出处:李振良
    分享

    原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”[[prototype]]”属性,函数对象有”prototype”属性,原型对象有”constructor”属性。

    为了弄清原型,以及原型相关的这些属性关系,就有了该文。

    相信通过该文一定能够清楚的认识到原型,现在就开始原型之旅吧。

    认识原型

    开始原型的介绍之前,首先来认识一下什么是原型?

    在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个” [[Prototype]]”内部属性,这个属性所对应的就是该对象的原型。

    “[[Prototype]]”作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了”__proto__”这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器”Object.getPrototype(object)”)。

    实例分析

    下面通过一个例子来看看原型相关概念:

    function Person(name, age){
        this.name = name;
        this.age = age;
    
        this.getInfo = function(){
            console.log(this.name + " is " + this.age + " years old");
        };
    }
    
    var will = new Person("Will", 28);

    在上面的代码中,通过了Person这个构造函数创建了一个will对象。下面就通过will这个对象一步步展开了解原型。

    Step 1: 查看对象will的原型

    通过下面代码,可以查看对象will的原型:

    console.log(will.__proto__);
    console.log(will.constructor);

    结果分析:

    “Person {}”对象就是对象will的原型,通过Chrome展开可以看到,”Person {}”作为一个原型对象,也有”__proto__”属性(对应原型的原型)。 在这段代码中,还用到了”constructor”属性。在JavaScript的原型对象中,还包含一个”constructor”属性,这个属性对应创建所有指向该原型的实例的构造函数。 通过”constructor”这个属性,我们可以来判断一个对象是不是数组类型
    function isArray(myArray) {
        return myArray.constructor.toString().indexOf("Array") > -1;
    }
    在这里,will对象本身并没有”constructor”这个属性,但是通过原型链查找,找到了will原型(will.__proto__)的”constructor”属性,并得到了Person函数。

    Step 2: 查看对象will的原型(will.__proto__)的原型

    既然will的原型”Person {}”也是一个对象,那么我们就同样可以来查看”will的原型(will.__proto__)的原型”。

    运行下面的代码:

    console.log(will.__proto__ === Person.prototype);
    console.log(Person.prototype.__proto__);
    console.log(Person.prototype.constructor);
    console.log(Person.prototype.constructor === Person);

    结果分析:

    首先看 “will.__proto__ === Person.prototype”,在JavaScript中,每个函数都有一个prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性),也就是说,所有实例的原型引用的是函数的prototype属性。了解了构造函数的prototype属性之后,一定就明白为什么第一句结果为true了。 prototype属性是函数对象特有的,假如不是函数对象,将不会有这样一个属性。 当通过”Person.prototype.__proto__”语句获取will对象原型的原型时候,将得到”Object {}”对象,后面将会看到所有对象的原型都将追溯到”Object {}”对象。 对于原型对象”Person.prototype”的”constructor”,根据前面的介绍,将对应Person函数本身。

    通过上面可以看到,“Person.prototype”对象和Person函数对象通过”constructor”和”prototype”属性实现了相互引用(后面会有图展示这个相互引用的关系)。

    Step 3: 查看对象Object的原型

    通过前一部分可以看到,will的原型的原型是”Object {}”对象。实际上在JavaScript中,所有对象的原型都将追溯到”Object {}”对象。

    下面通过一段代码看看”Object {}”对象:

    console.log(Person.prototype.__proto__ === Object.prototype);
    console.log(typeof Object);
    console.log(Object);
    console.log(Object.prototype);
    console.log(Object.prototype.__proto__);
    console.log(Object.prototype.constructor);

    通过下面的代码可以看到:

    Object对象本身是一个函数对象。 既然是Object函数,就肯定会有prototype属性,所以可以看到”Object.prototype”的值就是”Object {}”这个原型对象。 反过来,当访问”Object.prototype”对象的”constructor”这个属性的时候,就得到了Obejct函数。 另外,当通过”Object.prototype.__proto__”获取Object原型的原型的时候,将会得到”null”,也就是说”Object {}”原型对象就是原型链的终点了。

    Step 4: 查看对象Function的原型

    在上面的例子中,Person是一个构造函数,在JavaScript中函数也是对象,所以,我们也可以通过”__proto__”属性来查找Person函数对象的原型。

    console.log(Person.__proto__ === Function.prototype);
    console.log(Person.constructor === Function)
    console.log(typeof Function);
    console.log(Function);
    console.log(Function.prototype);
    console.log(Function.prototype.__proto__);
    console.log(Function.prototype.constructor);

    结果分析 :

    在JavaScript中有个Function对象(类似Object),这个对象本身是个函数;所有的函数(包括Function,Object)的原型(__proto__)都是”Function.prototype”。 Function对象作为一个函数,就会有prototype属性,该属性将对应”function () {}”对象。 Function对象作为一个对象,就有”__proto__”属性,该属性对应”Function.prototype”,也就是说,”Function.__proto__ === Function.prototype” 对于Function的原型对象”Function.prototype”,该原型对象的”__proto__”属性将对应”Object {}”

    对比prototype和__proto__

    对于”prototype”和”__proto__”这两个属性有的时候可能会弄混,”Person.prototype”和”Person.__proto__”是完全不同的。

    在这里对”prototype”和”__proto__”进行简单的介绍:

    对于所有的对象,都有__proto__属性,这个属性对应该对象的原型 对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)

    图解实例

    通过上面结合实例的分析,相信你一定了解了原型中的很多内容。

    但是现在肯定对上面例子中的关系感觉很凌乱,一会儿原型,一会儿原型的原型,还有Function,Object,constructor,prototype等等关系。

    现在就对上面的例子中分析得到的结果/关系进行图解,相信这张图可以让你豁然开朗。

    对于上图的总结如下:

    所有的对象都有”__proto__”属性,该属性对应该对象的原型 所有的函数对象都有”prototype”属性,该属性的值会被赋值给该函数创建的对象的”__proto__”属性 所有的原型对象都有”constructor”属性,该属性对应创建所有指向该原型的实例的构造函数 函数对象和原型对象通过”prototype”和”constructor”属性进行相互关联

    通过原型改进例子

    在上面例子中,”getInfo”方法是构造函数Person的一个成员,当通过Person构造两个实例的时候,每个实例都会包含一个”getInfo”方法。

    var will = new Person("Will", 28);
    var wilber = new Person("Wilber", 27);

    前面了解到,原型就是为了方便实现属性的继承,所以可以将”getInfo”方法当作Person原型(Person.__proto__)的一个属性,这样所有的实例都可以通过原型继承的方式来使用”getInfo”这个方法了。

    所以对例子进行如下修改:

    function Person(name, age){
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    };

    修改后的结果为:

    原型链

    因为每个对象和原型都有原型,对象的原型指向对象的父,而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。

    在”理解JavaScript的作用域链“一文中,已经介绍了标识符和属性通过作用域链和原型链的查找。

    这里就继续看一下基于原型链的属性查找。

    属性查找

    当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 “Object.prototype”), 假如仍然没有找到指定的属性,就会返回 undefined。

    看一个例子:

    function Person(name, age){
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.MaxNumber = 9999;
    Person.__proto__.MinNumber = -9999;
    
    var will = new Person("Will", 28);
    
    console.log(will.MaxNumber);
    // 9999
    console.log(will.MinNumber);
    // undefined

    在这个例子中分别给”Person.prototype “和” Person.__proto__”这两个原型对象添加了”MaxNumber “和”MinNumber”属性,这里就需要弄清”prototype”和”__proto__”的区别了。

    “Person.prototype “对应的就是Person构造出来所有实例的原型,也就是说”Person.prototype “属于这些实例原型链的一部分,所以当这些实例进行属性查找时候,就会引用到”Person.prototype “中的属性。

    属性隐藏

    当通过原型链查找一个属性的时候,首先查找的是对象本身的属性,假如找不到才会继续按照原型链进行查找。

    这样一来,假如想要覆盖原型链上的一些属性,我们就可以直接在对象中引入这些属性,达到属性隐藏的效果。

    看一个简单的例子:

    function Person(name, age){
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    };
    
    var will = new Person("Will", 28);
    will.getInfo = function(){
        console.log("getInfo method from will instead of prototype");
    };
    
    will.getInfo();
    // getInfo method from will instead of prototype

    对象创建方式影响原型链

    会到本文开始的例子,will对象通过Person构造函数创建,所以will的原型(will.__proto__)就是”Person.prototype”。

    同样,我们可以通过下面的方式创建一个对象:

    var July = {
        name: "July",
        age: 28,
        getInfo: function(){
            console.log(this.name + " is " + this.age + " years old");
        },
    }
    
    console.log(July.getInfo());

    当使用这种方式创建一个对象的时候,原型链就变成下图了,July对象的原型是”Object.prototype”也就是说对象的构建方式会影响原型链的形式。

    hasOwnProperty

    “hasOwnProperty”是”Object.prototype”的一个方法,该方法能判断一个对象是否包含自定义属性而不是原型链上的属性,因为”hasOwnProperty” 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

    相信你还记得文章最开始的例子中,通过will我们可以访问”constructor”这个属性,并得到will的构造函数Person。这里结合”hasOwnProperty”这个函数就可以看到,will对象并没有”constructor”这个属性。

    从下面的输出可以看到,”constructor”是will的原型(will.__proto__)的属性,但是通过原型链的查找,will对象可以发现并使用”constructor”属性。

    “hasOwnProperty”还有一个重要的使用场景,就是用来遍历对象的属性。

    function Person(name, age){
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    };
    
    var will = new Person("Will", 28);
    
    for(var attr in will){
        console.log(attr);
    }
    // name
    // age
    // getInfo
    
    for(var attr in will){
        if(will.hasOwnProperty(attr)){
            console.log(attr);
        }
    }
    // name
    // age

    总结

    该篇讲述了JavaScript中原型相关的概念,对于原型可以归纳出下面一些点:

    所有的对象都有”[[prototype]]”属性(通过__proto__访问),该属性对应对象的原型 所有的函数对象都有”prototype”属性,该属性的值会被赋值给该函数创建的对象的”__proto__”属性 所有的原型对象都有”constructor”属性,该属性对应创建所有指向该原型的实例的构造函数 函数对象和原型对象通过”prototype”和”constructor”属性进行相互关联

    还有要强调的是文章开始的例子,以及通过例子得到的一张”普通对象”,”函数对象”和”原型对象”之间的关系图,当你对原型的关系迷惑的时候,就想想这张图(或者重画一张当前对象的关系图),就可以理清这里面的复杂关系了。

    通过这些介绍,相信一定可以对原型有个清晰的认识。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    Cornell大神Kleinberg的经典教材《算法设计》是最好入门的算法教材

    从 Microsoft 下载中心安装 Windows 7 SP1 和 Windows Server 2008 R2 SP1 之前要执行的步骤

    Llama 2基于UCloud UK8S的创新应用

    火山引擎DataTester:如何使用A/B测试优化全域营销效果

    腾讯云、移动云继阿里云降价后宣布大幅度降价

    字节跳动数据平台论文被ICDE2023国际顶会收录,将通过火山引擎开放相关成果

    这个话题被围观超10000次,火山引擎VeDI如此解答

    误删库怎么办?火山引擎DataLeap“3招”守护数据安全

    IT头条

    平替CUDA!摩尔线程发布MUSA 4性能分析工具

    00:43

    三起案件揭开侵犯个人信息犯罪的黑灰产业链

    13:59

    百度三年开放2.1万实习岗,全力培育AI领域未来领袖

    00:36

    工信部:一季度,电信业务总量同比增长7.7%,业务收入累计完成4469亿元

    23:42

    Gartner:2024年全球半导体营收6559亿美元,AI助力英伟达首登榜首

    18:04

    技术热点

    iOS 8 中如何集成 Touch ID 功能

    windows7系统中鼠标滑轮键(中键)的快捷应用

    MySQL数据库的23个特别注意的安全事项

    Kruskal 最小生成树算法

    Ubuntu 14.10上安装新的字体图文教程

    Ubuntu14更新后无法进入系统卡在光标界面解怎么办?

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。