深入理解原型对象和原型链

标签: 前端开发 javascript 原型和原型链
14人阅读 评论(0) 收藏 举报
分类:

frontend/javascript/prototype_object_chain

原型对象和原型链在前端的工作中虽然不怎么显式的使用到,但是也会隐式的使用了,比如使用的jquery,vue等啦。在进入正题的时候,我们还是需要明白什么是__proto__prototype等知识点,主要讲解构造函数,这篇博文大多是问答形式进行…

原文请戳这里

问答环节

Javascript创建对象的方式?

也许你会说出工厂模式、构造函数模式、原型模式、组合使用构造函数和原型模式、动态原型模式、寄生构造函数模式和稳妥构造函数这些,但是我们可以对他们进行以下归类–属于函数创建对象。

我们可以简单的将创建对象的方式分为三种:函数创建对象、字面量创建、Object创建。当然,也可以只是分成两类:函数创建对象和字面量创建对象,因为new Object()中的Object是本身就是一个函数。

Object // f Object(){ [native code] }

什么是prototype?

function(注意是function哦)定义的对象有一个prototype属性,prototype属性又指向了一个prototype对象,注意prototype属性与prototype对象是两个不同的东西,要注意区别。用伪代码表示如下:

var function{
    prototype: prototype{} // function的prototype属性指向prototype对象
}

注意上面说的是function里面才会有prototype属性,而我们new出来的对象里面是没有的哦。

# function
function Fun(name){
    this.name = name;
}

Fun.prototype // {constructor:f}

var fun = new Fun('嘉明');
fun.prototype // undefined

# Object
Object.prototype // {constructor:f,__defineGetter__:f,...}

var object = new Object();
object.prototype // undefined

# 字面量,字面量可以理解没有prototype啦
var jack = {};
jack.prototype // undefined

proto是什么?

在官方的es5种,定义了一个名叫做[[prototype]]的属性,每个对象(除了null)都拥有这样一个属性,这个属性是一个指针,它指向一个名叫做原型对象的内存堆。而原型对象也是一个对象,因此又含有自己的[[prototype]]属性,又指向下一个原型对象,终点指向我们的Object.prototype对象。

注意⚠️ 这里使用的是[[prototype]],而并非proto。可是他们是同一个东西哈:[[prototype]]是官方所定义的属性,而proto是浏览器(就是任性)自己对[[prototype]]所做的实现。

分三种情况来说对象内部的__proto__

  1. 使用字面量定义一个普通对象: var foo = {}
  2. 创建一个函数: function Foo(){};包含Object()啦
  3. 创建对象实例: var foo = new Foo();

情况一:{}

var foo = {};
foo.__proto__; // {}
foo.__proto__ === Object.prototype; // true
foo.hasOwnProperty('prototype'); // false 函数才有prototype属性
foo.hasOwnProperty('__proto__'); // false
Object.prototype.hasOwnProperty('__proto__'); // true

代码的最后一行,一个是返回了false,另一个是true。⚠️因为它并不存在于foo对象(foo.__proto__)或者Foo.prototype(Foo.prototype.__proto__)或者Foo(Foo.__proto__)中【下面情况二和三会有代码验证】,实际上,它是来自于Object.prototype,与其说是一个属性,不如说是一个getter/setter。

情况二:function Foo(){}

1. function Foo(){};
2. Foo.__proto__; // [Function]
3. Foo.__proto__ === Object.prototype; // false
4. Foo.__proto__.__proto__ === Object.prototype; // true
5. Foo.prototype.__proto__ === Object.prototype; // true 函数的原型对象指向
6. Foo.__proto__ == Foo.prototype; //false
7. Foo.hasOwnProperty('__proto__'); // false
8. Foo.hasOwnProperty('prototype'); // true

在函数中,通过上面代码2,3,4,5可以知道Foo.proto可以理解为指向了Foo.prototype(广义上理解),而实际上两个又有差别(狭义上,第6点可以说明,欢迎补充)。然后就是每个函数都有一个默认的prototype属性,其指向函数的原型对象。

情况三:对象实例 new Foo()

function Foo(){};
var foo = new Foo();
foo.__proto__; // Foo {}
foo.__proto__ === Foo.prototype ; true
foo.hasOwnProperty('prototype'); false
foo.hasOwnProperty('__proto__'); false

上面可知,实例中是没有prototype这个属性的,对比上面的三种情况,也说明了只有在函数中才默认创建了prototype属性,而且指向了相应的函数原型对象。

constructor是什么?

在javascript语言中,constructor属性是专门为function而设计的,它存在于每一个function的prototype属性中,这个constructor保存了指向function的一个引用。

function F(){
    // some code
}

# javascript内部会执行如下的动作
# 1.为该函数添加一个原型(即prototype)属性
# 2.为prototype对象额外添加一个constructor属性,并且该属性保存指向函数F的一个引用

对象的实例中也有一个constructor属性(从prototype那里获取的),每一个对象实例都可以通过constructor对象访问它的构造函数,如下:

var f = new F();
f.constructor === F; // true
f.constructor === F.prototype.constructor; // true

既然可以访问实例的类型f.constructor,那么我们就可以对实例进行特殊的处理啦:

if(f.constructor == F){
    // some code
}

不过别这样操作,因为constructor是不稳定的(见下文对象中的constructor的作用是什么呢?),一般不会采取上面的这种操作,而是通过instanceof

if(f instanceof F){
    // some code
}

对象中的constructor的作用是什么呢?

这里推荐贺师俊前辈的回答,原文复制如下:

constructor属性不影响任何javascript的内部属性。instanceof检测对象的原型链,通常你是无法修改的(不过某些引擎通过私有的__proto__属性暴露出来)。

constructor其实没有什么用,只是javascript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这种习惯。

例子解析:

delete Object.prototype.constructor; // true
({}).constructor; // undefined
({}) instanceof Object; // true

原型链的最高指向?

《javascript高级程序设计》中有说到所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。那么原型的最高指向就是Object了嘛?你可以理解是Object,可是我认为是null。

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

最高指向是Object/null,无伤大雅。

实例和原型的关系?

当读取实例的属性时,如果找不到实例的属性,就会查找与对象关联的原型的属性,如果还是查找不到,就查找原型的原型,一直到顶级为止。

function Person(){
}
Person.prototype.name = "嘉明";

var person = new Person();

person.name = "jiaming";
console.log(person.name); // "jiaming"

// 删除实例的属性后
delete person.name;
console.log(person.name); // "嘉明"

// 追加一个疑问  在__proto__中加属性会覆盖原来的嘛
person.__proto__.name = "嘉";
console.log(person.name); // "嘉"   证明成功,不建议这样修改,毕竟__proto__是浏览器厂商实现的,非标准的

// 再追加一个疑问 __proto__添加的属性或者方法是放在对象的原型上的嘛
var another_person = new Person();
console.log(another_person.name); // "嘉"  证明是放在对象的原型上的

原型的原型呢?

属性或者方法在自己的原型上没有找到的话,那就要跑到原型上去找啦。之前有提到过所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。

那么一个构造函数function Person(){}就存在这样的一个关系,实例出来的var person = new Person()person通过proto指向构造函数的原型Person.prototype,然后构造函数的原型指向Object的原型,即是Person.prototype.__proto__指向Object.prototype

总结下呗

嗯,还是针对构造函数来说哈,将上面提到的知识点汇总一下啦。上面都是纯文字说明,下面就配上图片好好理解下。

先上相关代码:

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

var person = new Person("嘉明");
person.age = 12;
person.sayName(); // "嘉明"
console.log(person.name.toString()); // "嘉明"

var another_person = new Person("jiaming");
another_person.sayName();

上面代码中,相关的关系如下图

frontend/javascript/prototype_chain_summerize_img01

实验环节

小demo是使用canvas画出小星光,代码如下:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>canvas</title>
    <style>
        body{
            margin: 0;
            padding: 0;
            position: relative;
        }
        #myCanvas{
            position: absolute;
            left: 50%;
            top: 50%;
            background: #000;
            margin-left: -300px;
            margin-top: -150px;
        }
    </style>
</head>
<body>
    <canvas id="myCanvas" width="600" height="300" style="border: 1px solid #000;">

    </canvas>
    <script src="path/to/canvas.js"></script>
</body>
</html>
window.onload = function(){
    var c = document.getElementById('myCanvas');

    var grd = ""; // 渐变的颜色

    // 上下文
    var context = c.getContext("2d");

    if(context){
        // x,y,r 坐标和半径
        function Star(x,y,r){
            this.x = x;
            this.y = y;
            this.r = r;
            this.init(this.x,this.y,this.r);
        }

        // 绘制星星
        Star.prototype.init = function(x,y,r){


            context.beginPath();

            // 渐变颜色
            grd = context.createRadialGradient(x,y,r-2,x,y,r+2)
            grd.addColorStop(0, 'white');
            grd.addColorStop(1, 'yellow');
            context.fillStyle=grd;

            //  画圆
            context.arc(x,y,r,0,2*Math.PI);

            // 填充颜色
            context.fill();

            context.closePath();


        }

        // 创建星星
        for(var i = 0; i < 300; i++){
            var x = Math.floor(Math.random()*600);
            var y = Math.floor(Math.random()*300);
            var r = Math.floor(Math.random()*3)+2;
            new Star(x,y,r)
        }
    }else{
        var div = document.createElement("div");
        div.innerHTML = "您的浏览器不支持canvas,请升级浏览器!";
        document.getElementsByTagName("body")[0].appendChild(div);
    }
}

实现的简单效果如下图哈(ps,您可自行验证哈,改善啥的):

frontend/javascript/prototype_chain_demo

原文请戳这里

查看评论

深入理解远程调用之Hessian

-
  • 1970年01月01日 08:00

个人对"原型"和"原型链"的理解

Javascript语言可能太过灵活,导致一些学C#学Java等姑且说叫"正统的面向对象的语言"的人觉得Javascript面向对象的部分凌乱不堪,上网看别人对原型和原型链的理解都是各抒己见,各有各的...
  • cuiyh1993
  • cuiyh1993
  • 2015-07-22 23:07:10
  • 2220

一个例子让你彻底明白原型对象和原型链

参考博文:http://www.jianshu.com/p/aa1ebfdad661 function Person () { this.name = 'John'; } ...
  • sweetllh
  • sweetllh
  • 2017-06-29 18:45:11
  • 553

深入理解JavaScript系列(二): 原型、原型链与继承

1.原型 1.什么是原型 我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方...
  • u012422829
  • u012422829
  • 2016-03-28 17:01:03
  • 1051

深入理解原型链的本质

原型链是作为实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 实现原型链的代码如下:function Super() { this.property = ...
  • code_ja
  • code_ja
  • 2016-06-29 15:55:28
  • 868

完整原型链详细图解(构造函数、原型、实例化对象)

一、首先说一下什么是构造函数: 构造函数:用来在创建对象时初始化对象。特点:构造函数名一般为大写字母开头;与new运算符一起使用来实例化对象。 举例: function Person(){} ...
  • SpicyBoiledFish
  • SpicyBoiledFish
  • 2017-05-03 14:28:52
  • 2265

js原型对象与原型链

转自:js原型对象与原型链 原型对象与原型链 正如第三章提到的,JavaScript对象是一个属性的集合,另外有一个隐式的对象:原型对象。原型的值可以是一个对象或者null。一般的引擎实现中,...
  • chenjh213
  • chenjh213
  • 2015-01-08 16:04:50
  • 757

深入理解JS继承和原型链

对于那些熟悉基于类的面向对象语言(Java 或者 C++)的开发者来说,JavaScript 的语法是比较怪异的,这是由于 JavaScript 是一门动态语言,而且它没有类的概念( ES6 新增了...
  • zls986992484
  • zls986992484
  • 2016-12-17 11:18:40
  • 3274

一个例子让你明白原型对象和原型链

开篇 之前对js中的原型链和原型对象有所了解,每当别人问我什么是原型链和原型对象时,我总是用很官方(其实自己不懂)的解释去描述。有一句话说的好:如果你不能把一个很复杂的东西用最简单的话语描述出来,...
  • kkkkkxiaofei
  • kkkkkxiaofei
  • 2015-06-12 17:51:24
  • 29964

从__proto__和prototype来深入理解JS对象和原型链

就标题而言,这是七八篇里起得最满意的,高大上,即使外行人也会不明觉厉!  不过不是开玩笑,本文的确打算从__proto__和prototype这两个容易混淆来理解JS的终极命题之一:对象与原型链...
  • Magneto7
  • Magneto7
  • 2017-04-19 10:29:57
  • 420
    个人资料
    等级:
    访问量: 185
    积分: 36
    排名: 193万+
    文章分类
    文章存档
    最新评论