JavaScript 私有成员

Class field declarations for JavaScript(JavaScript 类的字段声明)现在已进入了 stage-3,个中包括一项 OOP 开辟者都很关注的内容:Private fields。JavaScript 一向没有私有成员并不是没有缘由,所以这一发起给 JavaScript 带来了新的应战。但同时,JavaScript 在 ES2015 宣布的时刻已在斟酌私有化的题目了,所以要完成私有成员也并不是毫无基本。

笔者在专栏《JavaScript 全栈工程师养成记》的第四章讲到了原型 OOP 关联和继承 OOP 关联的症结区分。本日这里就研究一下 JavaScript 私有成员的题目。

起首挖个坑 —— 这是一段 JS 代码,BusinessView 中要干两件事变,即对表单和舆图举行规划。

代表将
_ 前缀商定为私有

class BaseView {
    layout() {
        console.log("BaseView Layout");
    }
}

class BusinessView extends BaseView  {
    layout() {
        super.layout();
        this._layoutForm();
        this._layoutMap();
    }

    _layoutForm() {
        // ....
    }

    _layoutMap() {
        // ....
    }
}

然后,由于营业的生长,发明有许多视图都存在舆图规划。这里选用继承的体式格局来完成,所以从 BusinessView 中把舆图相干的内容笼统成一个基类叫 MapView

class MapView extends BaseView {
    layout() {
        super.layout();
        this._layoutMap();
    }

    _layoutMap() {
        console.log("MapView layout map");
    }
}

class BusinessView extends MapView {
    layout() {
        super.layout();
        this._layoutForm();
        this._layoutMap();
    }

    _layoutForm() {
        // ....
    }

    _layoutMap() {
        console.log("BusinessView layout map");
    }
}

上面这两段代码是很典范的基于继承的 OOP 头脑,本意是希冀各个条理的类都能够经由历程 layout() 来举行各条理应当担任的规划使命。但抱负和实际总是有差异的,在 JavaScript 中运转就会发明 BusinessView._layoutMap() 被实行了两次,而 MapView._layoutMap() 未实行。为何?

虚函数

JavaScript 中假如在先人和子孙类中定义了雷同的称号的要领,默许会挪用子孙类中的这个要领。假如想挪用先人类中的同名要领,须要在子孙类中经由历程 super. 来挪用。

这里能够剖析一下这个历程:

在子类建立对象的时刻,其类和一切先人类的定义都已加载了。这个时刻

  • 挪用 BusinessView.layout()
  • 找到 super.layout(),最先挪用 MapView.layout()
  • MapView.layout() 中挪用this._layoutMap()

    • 因而从当前对象(BusinessView 对象)寻觅 _layoutMap()
    • 找到,挪用它

你看,由于 BusinessView 定义了 _layoutMap,所以压根都没去搜刮原型链。对的,这是基于原型关联的 OOP 的范围。假如我们看看 C# 的处置惩罚历程,就会发明有所差别

  • 挪用 BusinessView.layout()
  • 找到 base.layout(),最先挪用 MapView.layout()
  • MapView.layout() 中挪用 this._layoutMap()

    • MapView 中找到 _layoutMap()
    • 搜检是不是虚函数

      • 假如是,往子类找到末了一个重载(override)函数,挪用
      • 假如不是,直接挪用

发明区分了吗?症结是在于推断“虚函数”。

然则,这跟私有成员又有什么关联呢?由于私有函数肯定不是虚函数,所以在 C# 中,假如将 _layoutMap 定义为私有,那 MapView.layout() 挪用的就肯定是 MapView._layoutMap()

虚函数的观点有点小庞杂。不过能够简朴理解为,假如一个成员要领被声明为虚函数,在挪用的时刻就会延着其虚函数链找到末了的重载来举行挪用。

JavaScript 中虽然商定 _ 前缀的是私有,那也只是正人之约,它本质上依然不是私有。正人之约对人有用,计算机又不晓得你有这个商定……。然则,假如 JavaScript 真的完成了私有成员,那末计算机就晓得了,_layoutMap() 是个私有要领,应当挪用本类中的定义,而不是去寻觅子类中的定义。

处理当下的私有化题目

JavaScript 当下没有私有成员,然则我们又须要切时有用地处理私有成员题目,怎样办?当然有方法,用 Symbol 和闭包来处理。

注重,这里的闭包不是指点在函数函数中天生闭包,请继承往下看

起首搞清楚,我们变通的对待这个私有化题目 —— 就是让先人类挪用者在挪用某个要领的时刻,它不会先去子类中寻觅。这个题目从语法上处理不了,JavaScript 就是要从详细的实例从后往前往寻觅指定称号的要领。然则,假如找不到这个要领名呢?

之所以能找到,由于要领名是字符串。一个字符串在全局作用域内都示意着一样的意义。然则 ES2015 带来了 Symbol,它必需实例化,而且每次实例化出来肯定代表着差别的标识 —— 假如我们将类定义在一个闭包中,在这个闭包中声明一个 Symbol,用它来作为私有成员的称号,题目就处理了,比方

const MapView = (() => {
    const _layoutMap = Symbol();

    return class MapView extends BaseView {
        layout() {
            super.layout();
            this[_layoutMap]();
        }

        [_layoutMap]() {
            console.log("MapView layout map");
        }
    }
})();

const BusinessView = (() => {
    const _layoutForm = Symbol();
    const _layoutMap = Symbol();

    return class BusinessView extends MapView {
        layout() {
            super.layout();
            this[_layoutForm]();
            this[_layoutMap]();
        }

        [_layoutForm]() {
            // ....
        }

        [_layoutMap]() {
            console.log("BusinessView layout map");
        }
    }
})();

而当代基于模块的定义,甚至连闭包都能够省了(模块体系会自动关闭作用域)

const _layoutMap = Symbol();

export class MapView extends BaseView {
    layout() {
        super.layout();
        this[_layoutMap]();
    }

    [_layoutMap]() {
        console.log("MapView layout map");
    }
}
const _layoutForm = Symbol();
const _layoutMap = Symbol();

export class BusinessView extends MapView {
    layout() {
        super.layout();
        this[_layoutForm]();
        this[_layoutMap]();
    }

    [_layoutForm]() {
        // ....
    }

    [_layoutMap]() {
        console.log("BusinessView layout map");
    }
}

革新事后的代码就能够按预期输出了:

BaseView Layout
MapView layout map
BusinessView layout map

跋文

笔者在多年开辟历程当中养成了剖析和处理题目的一系列思维习惯,所以经常能够敏捷的透过征象看到须要处理的本质性题目,并基于现有前提来处理它。确切,Symbol 涌现的来由之一就是处理私有化题目,然则为何要用以及怎样用就须要去剖析和思索了。

进修能够让人处理雷同的题目,但思索能够让人处理类似的题目。迎接读者们来进修笔者的专栏《JavaScript 全栈工程师养成记》,并随着笔者一同思索、剖析和处理软件开辟历程当中的若干题目。

    原文作者:边城
    原文地址: https://segmentfault.com/a/1190000015987646
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞