最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
当前位置: 首页 - 科技 - 知识百科 - 正文

探索JavaScript中私有成员的相关知识

来源:懂视网 责编:小采 时间:2020-11-27 21:55:15
文档

探索JavaScript中私有成员的相关知识

探索JavaScript中私有成员的相关知识:坑 首先挖个坑 —— 这是一段 JS 代码,BusinessView 中要干两件事情,即对表单和地图进行布局。 代表将 _ 前缀约定为私有 class BaseView { layout() { console.log(BaseView Layout); } } class BusinessView ex
推荐度:
导读探索JavaScript中私有成员的相关知识:坑 首先挖个坑 —— 这是一段 JS 代码,BusinessView 中要干两件事情,即对表单和地图进行布局。 代表将 _ 前缀约定为私有 class BaseView { layout() { console.log(BaseView Layout); } } class BusinessView ex


首先挖个坑 —— 这是一段 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 出现的理由之一就是解决私有化问题,但是为什么要用以及怎么用就需要去分析和思考了。

    声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文档

    探索JavaScript中私有成员的相关知识

    探索JavaScript中私有成员的相关知识:坑 首先挖个坑 —— 这是一段 JS 代码,BusinessView 中要干两件事情,即对表单和地图进行布局。 代表将 _ 前缀约定为私有 class BaseView { layout() { console.log(BaseView Layout); } } class BusinessView ex
    推荐度:
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top