专栏首页醉梦轩Javascript中的原型链分析

Javascript中的原型链分析

0x00 前言

Javascript中的prototype是一个十分重要的概念,但是网上的教程一般分析得比较绕,结果越看越晕,反而变得更加难以理解了。

本文尝试由浅入深,从实验入手,来深入地理解这一概念。

0x01 函数与对象

函数是JS中最为重要的一个概念,下面是创建函数最简单的方法:

function func(){
    return 0;
}

通过Chrome开发者工具,可以得到以下输出:

> typeof func
< "function"
> func instanceof Function
< true
> func instanceof Object
< true

可以看出,funcFunction的一个实例,同时也是Object的一个实例。这点可以理解成Function本质上也是Object的一种。

> typeof Function
< "function"
> typeof Object
< "function"

再来看这段输出,按照通常OOP语言的理解,FunctionObject的类型应该是class之类的值,但偏偏这里返回的是function。这是为什么呢?

我们知道,js中class的概念是在ES6中才出现的,可以通过以下代码创建一个class

class MyClass {
    constructor(name) {
        this.name = name;
    }
    show(){
        console.log(this.name);
    }
}

var obj = new MyClass('drunkdream');
obj.show();

现在来测试一下obj实例的相关情况:

> typeof obj
< "object"
> obj instanceof MyClass
< true
> obj instanceof Object
< true
> typeof MyClass
< "function"
> MyClass instanceof Function
< true

可以看出,obj的确是MyClass的一个实例。但是,奇怪的是:MyClass的类型竟然是function,这点和其它语言的确不太一样。

这是因为:

js中并没有真正的class的概念,class仅仅是function的一种语法糖而已。

来看下在ES5中一般怎么构造一个class的。

function MyClass(name) {
    this.name = name;
}

MyClass.prototype.show = function () {
    console.log(this.name);
}

这种写法可以实现和上面那段代码相同的功能,但是很明显,MyClass真的是一个function。也就是说:new一个function得到的其实是一个对象。这和其它语言差异是比较大的。

prototype在其中就是扮演了添加类的成员函数的作用。

其实,将上面的代码改成:

function MyClass(name) {
    this.name = name;
    this.show = function () {
        console.log(this.name);
    }
}

这样的形式对于使用者也是完全没有问题的,差别只是每次实例化都会创建出一个show函数,显然这种写法是不好的。

0x02 prototype与proto

prototype到底是个什么样的存在呢?

> MyClass.prototype
< {show: ƒ, constructor: ƒ}
    show: ƒ ()
    constructor: ƒ MyClass(name)
    __proto__: Object
> typeof MyClass.prototype
< "object"
> MyClass.prototype.constructor === MyClass
< true
> MyClass.prototype.__proto__
< {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
    constructor: ƒ Object()
    hasOwnProperty: ƒ hasOwnProperty()
    isPrototypeOf: ƒ isPrototypeOf()
    propertyIsEnumerable: ƒ propertyIsEnumerable()
    toLocaleString: ƒ toLocaleString()
    toString: ƒ toString()
    valueOf: ƒ valueOf()
    __defineGetter__: ƒ __defineGetter__()
    __defineSetter__: ƒ __defineSetter__()
    __lookupGetter__: ƒ __lookupGetter__()
    __lookupSetter__: ƒ __lookupSetter__()
    get __proto__: ƒ __proto__()
    set __proto__: ƒ __proto__()

上面这段看起来有点绕,需要仔细思索一下。

可以看出,prototype本质上是一个对象,必须要包含constructor构造函数和__proto__对象。

constructor其实就是MyClass函数本身,而__proto__对象看起来就有些神秘了。不过从__proto__.constructor可以看出,它其实就是Object。是不是觉得__proto__指向的是当前类的基类呢?

我们再来测试一下:

> class MyClass1 extends String{}
> MyClass1.prototype.__proto__.constructor == String
< true

看来的确是这样的,只不过由于js中的类本质上都是function,而每个function都有一个原型,通过这种方式将原型链接起来,就起到了类继承的作用。

0x03 将对象变成函数

下面是网上找的一段代码:

function classcallable(cls) {
    /*
     * Replicate the __call__ magic method of python and let class instances
     * be callable.
     */
    var new_cls = function () {
        var obj = Object.create(cls.prototype);
        // create callable
        // we use func.__call__ because call might be defined in
        // init which hasn't been called yet.
        var func = function () {
            return func.__call__.apply(func, arguments);
        };
        func.__proto__ = obj;
        // apply init late so it is bound to func and not cls
        cls.apply(func, arguments);
        return func;
    }
    new_cls.prototype = cls.prototype;
    return new_cls
}

它可以将一个类实例类型从object变成function

var s = new String();
console.log(typeof s);

var s = new classcallable(String)();
console.log(typeof s);

输出结果为:

object
function

也就是说,使用classcallable之后创建的对象,可以当做函数来调用。我们分析一下这里面的原因。

在js中是允许在类的构造函数中返回一个function的,可以使用以下代码进行测试:

function MyClass(flag){
    var func = function(){
        console.log("call func");
    }
    if(flag === 1)
        return func;
    else
        return 0;
}

console.log(typeof new MyClass(0));
console.log(typeof new MyClass(1));

输出结果为:

object
function

因此,只要修改构造函数的返回值,就可以改变创建出的实例类型,这里正是用了这种方法。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • TOS中安装 SuperSU

    drunkdream
  • 一次Art Hook失败问题的跟进

    最近在使用一款Art Hook框架对应用进行Hook的时候发现,函数Hook之后却总是没有被触发,于是怀疑是被dex2oat做了Inline处理。

    drunkdream
  • mongodb常用操作命令

    mongodb是一款基于分布式文件存储的数据库,具有高性能、可扩展、易部署、易使用等特点。官方也提供了丰富的命令行工具来操作。

    drunkdream
  • [PHP] 重回基础(Array相关函数)

    使用函数array_map(),使每个元素都调用一下自定义函数,参数:String类型函数名称,数组

    陶士涵
  • jq使用splice删除数组元素出错的解决方法

    splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。

    仙士可
  • 包子大道消息-无人车烧钱榜单-FB开人-我们是东亚病夫? -Leetcode solution 243

    能够在无人车领域工作可能是很多程序员的梦想,极具挑战的技术问题,业界顶配的薪资以及关于无人驾驶未来无尽的想象空间。那么问题来了,你应该去哪家无人车公司效力呢?我...

    包子面试培训
  • Hbase优化思路

    1.rowkey设计 2.手动split,手动compant 3.开启booleamfilter 4.采用压缩 5.预分区 6.并发读写 ...

    shengjk1
  • 迭代式开发使用方法总结

          为什么我在这里主要讨论迭代式软件开发?本文在此抛开千篇一律的理论,拟就根据多年的实践,总结出一套比较务实、可操作性强的方法,以期望在有限的资源下确保...

    庞小明
  • Silverlight初级教程-绘图布局

    Silverlight初级教程 绘图布局 正如之前所说Blend是和flash很像的东西。在这里将介绍一下如何在Blend中绘图。 这里的Blend中“舞台...

    用户1172164
  • Javascript Prototypes之旅(A Plain English Guide to JavaScript Prototypes译文)

      当我第一次学习Javascript的对象模型时,我的反应时困惑。因为这是我第一次接触基于原型的语言,所以我完完全全被原型弄得糊里糊涂(译者语:在看这篇文章前...

    ^_^肥仔John

扫码关注云+社区

领取腾讯云代金券