• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-04-12 23:25 Aet 隐藏边栏 |   抢沙发  1 
文章评分 1 次,平均分 5.0

私有的和受保护的属性和方法

概述

  1. 面向对象编程最重要的原则之一 —— 将内部接口与外部接口分隔开来
  2. JavaScript 中,有两种类型的对象字段(属性和方法):
    1. 公共的:可从任何地方访问。它们构成了外部接口。到目前为止,我们只使用了公共的属性和方法
    2. 私有的:只能从类的内部访问。这些用于内部接口
    3. 在许多其他编程语言中,还存在“受保护”的字段:
      只能从类的内部和基于其扩展的类的内部访问(例如私有的,但可以从继承的类进行访问)
      受保护的字段不是在语言级别的 JavaScript 中实现的,但实际上它们非常方便,因为它们是在 JavaScript 中模拟的类定义语法

内部接口和外部接口

  1. 内部接口 —— 可以通过该类的其他方法访问,但不能从外部访问的方法和属性
  2. 外部接口 —— 也可以从类的外部访问的方法和属性

受保护的

  1. 首先,让我们做一个简单的咖啡机类:
    1. 现在,属性 waterAmountpower 是公共的。我们可以轻松地从外部将它们 get/set 成任何值

  1. 让我们将 waterAmount 属性更改为受保护的属性,以对其进行更多控制
    1. 例如,我们不希望任何人将它的值设置为小于零的数
    2. 受保护的属性通常以下划线 _ 作为前缀
    3. 这不是在语言级别强制实施的,但是程序员之间有一个众所周知的约定,即不应该从外部访问此类型的属性和方法

只读的

  1. 对于 power 属性,让我们将它设为只读
    1. 有时候一个属性必须只能被在创建时进行设置,之后不再被修改
    2. 咖啡机就是这种情况:功率永远不会改变
    3. 要做到这一点,我们只需要设置 getter,而不设置 setter:

getter/setter 函数

  1. 这里我们使用了 getter/setter 语法
  2. 但大多数时候首选 get.../set... 函数,像这样:
    1. 这看起来有点长,但函数更灵活。它们可以接受多个参数(即使我们现在还不需要)
    2. 另一方面,get/set 语法更短,所以最终没有严格的规定,而是由你自己来决定

注意1

  1. 受保护的字段是可以被继承的
    1. 如果我们继承 class MegaMachine extends CoffeeMachine,那么什么都无法阻止我们从新的类中的方法访问 this._waterAmountthis._power
    2. 所以受保护的字段是自然可被继承的

私有的

  1. 这是一个最近添加到 JavaScript 的特性
  2. 私有属性和方法应该以 # 开头。它们只在类的内部可被访问
    1. 例如,这儿有一个私有属性 #waterLimit 和检查水量的私有方法 #fixWaterAmount

  1. 私有字段与公共字段不会发生冲突。我们可以同时拥有私有的 #waterAmount 和公共的 waterAmount 字段
    1. 例如,让我们使 waterAmount 成为 #waterAmount 的一个访问器:

  1. 与受保护的字段不同,私有字段由语言本身强制执行
    1. 但是如果我们继承自 CoffeeMachine,那么我们将无法直接访问 #waterAmount。我们需要依靠 waterAmount getter/setter
    2. 在许多情况下,这种限制太严重了
      如果我们扩展 CoffeeMachine,则可能有正当理由访问其内部
      这就是为什么大多数时候都会使用受保护字段,即使它们不受语言语法的支持

注意2

  1. 私有字段不能通过 this[name] 访问
    1. 私有字段很特别
    2. 正如我们所知道的,通常我们可以使用 this[name] 访问字段:
    3. 对于私有字段来说,这是不可能的:this['#name'] 不起作用。这是确保私有性的语法限制

扩展内建类

概述

  1. 内建的类,例如 ArrayMap 等也都是可以扩展的(extendable
  2. 例如,这里有一个继承自原生 Array 的类 PowerArray

  1. 请注意一个非常有趣的事儿
    1. 内建的方法例如 filtermap 等 —— 返回的正是子类 PowerArray 的新对象
    2. 它们内部使用了对象的 constructor 属性来实现这一功能

  1. arr.filter() 被调用时,它的内部使用的是 arr.constructor 来创建新的结果数组,而不是使用原生的 Array
    1. 我们可以在结果数组上继续使用 PowerArray 的方法
    2. 甚至,我们可以定制这种行为
    3. 我们可以给这个类添加一个特殊的静态 getter Symbol.species,它会返回 JavaScript 在内部用来在 mapfilter 等方法中创建新实体的 constructor
    4. 如果我们希望像 mapfilter 这样的内建方法返回常规数组,我们可以在 Symbol.species 中返回 Array,就像这样:

  1. 其他集合的工作方式类似
    1. 其他集合,例如 MapSet 的工作方式类似。它们也使用 Symbol.species

内建类没有静态方法继承

  1. 内建对象有它们自己的静态方法,例如 Object.keysArray.isArray
    1. 如我们所知道的,原生的类互相扩展。例如,Array 扩展自 Object
    2. 通常,当一个类扩展另一个类时,静态方法和非静态方法都会被继承
    3. 但内建类却是一个例外。它们相互间不继承静态方法
  2. 例如,ArrayDate 都继承自 Object,所以它们的实例都有来自 Object.prototype 的方法
    1. Array.[[Prototype]] 并不指向 Object,所以它们没有例如 Array.keys()(或 Date.keys())这些静态方法

类检查:instanceof

概述

  1. instanceof 操作符用于检查一个对象是否属于某个特定的 class
    1. 同时,它还考虑了继承
  2. 在许多情况下,可能都需要进行此类检查
    1. 例如,它可以被用来构建一个 多态性(polymorphic) 的函数,该函数根据参数的类型对参数进行不同的处理

instanceof操作符

  1. 语法
    1. 如果 obj 隶属于 Class 类(或 Class 类的衍生类),则返回 true

  1. 例如:

  1. 它还可以与构造函数一起使用:

  1. 与诸如 Array 之类的内建 class 一起使用:
    1. 有一点需要留意,arr 同时还隶属于 Object 类。因为从原型上来讲,Array 是继承自 Object
    2. 通常,instanceof 在检查中会将原型链考虑在内

  1. 此外,我们还可以在静态方法 Symbol.hasInstance 中设置自定义逻辑

obj instanceof Class 的执行过程大致如下:

  1. 如果这儿有静态方法 Symbol.hasInstance,那就直接调用这个方法:

  1. 大多数 class 没有 Symbol.hasInstance。在这种情况下,标准的逻辑是:使用 obj instanceOf Class 检查 Class.prototype 是否等于 obj 的原型链中的原型之一
    1. 换句话说就是,一个接一个地比较:

  1. 上面那个例子中,rabbit.__proto__ === Rabbit.prototype,所以立即就给出了结果
    1. 在继承的例子中,匹配将在第二步进行:

objA.isPrototypeOf(objB)

  1. 这里还要提到一个方法 objA.isPrototypeOf(objB)
    1. 如果 objA 处在 objB 的原型链中,则返回 true
    2. 所以,可以将 obj instanceof Class 检查改为 Class.prototype.isPrototypeOf(obj)

使用 Object.prototype.toString 来揭示类型

  1. 一个普通对象被转化为字符串时为 [object Object]
    1. 这是通过 toString 方法实现的

  1. 这儿有一个隐藏的功能,该功能可以使 toString 实际上比这更强大
    1. 我们可以将其作为 typeof 的增强版或者 instanceof 的替代方法来使用
    2. 按照 规范所讲,内建的 toString 方法可以被从对象中提取出来,并在任何其他值的上下文中执行。其结果取决于该值
    3. 对于 number 类型,结果是 [object Number]
    4. 对于 boolean 类型,结果是 [object Boolean]
    5. 对于 null[object Null]
    6. 对于 undefined[object Undefined]
    7. 对于数组:[object Array]
    8. 等(可自定义)

  1. 这里我们用到了 call 方法来在上下文 this=arr 中执行函数 objectToString
    1. 在内部,toString 的算法会检查 this,并返回相应的结果

Symbol.toStringTag

  1. 可以使用特殊的对象属性 Symbol.toStringTag 自定义对象的 toString 方法的行为

  1. 对于大多数特定于环境的对象,都有一个这样的属性
    1. 下面是一些特定于浏览器的示例:

Mixin 模式

概述

  1. JavaScript 中,我们只能继承单个对象
    1. 每个对象只能有一个 [[Prototype]]。并且每个类只可以扩展另外一个类
  2. 但是有些时候这种设定(译注:单继承)会让人感到受限制
    1. 例如,我有一个 StreetSweeper 类和一个 Bicycle 类,现在想要一个它们的混合体:StreetSweepingBicycle
    2. 或者,我们有一个 User 类和一个 EventEmitter 类来实现事件生成(event generation),并且我们想将 EventEmitter 的功能添加到 User 中,以便我们的用户可以触发事件(emit event
  3. 有一个概念可以帮助我们,叫做 “mixin
    1. 是一个类,其方法可被其他类使用,而无需继承
    2. 换句话说,mixin 提供了实现特定行为的方法,但是我们不单独使用它,而是使用它来将这些行为添加到其他类中

Mixin示例

  1. JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并到任何类的原型中
  2. 例如,这个名为 sayHiMixinmixin 用于给 User 添加一些“语言功能”:

  1. 这里没有继承,只有一个简单的方法拷贝
    1. 因此,我们可以让 User 在继承另一个类的同时,使用 mixin 来 “mix-in”(混合)其它方法,就像这样:

  1. Mixin 可以在自己内部使用继承
    1. 例如,这里的 sayHiMixin 继承自 sayMixin
    2. 注意,在 sayHiMixin 内部对父类方法 super.say() 的调用(在标有 (*) 的行)会在 mixin 的原型中查找方法,而不是在 class 中查找
    3. 这是因为方法 sayHisayBye 最初是在 sayHiMixin 中创建的。因此,即使复制了它们,但是它们的 [[HomeObject]] 内部属性仍引用的是 sayHiMixin
    4. super[[HomeObject]].[[Prototype]] 中寻找父方法时,意味着它搜索的是 sayHiMixin.[[Prototype]],而不是 User.[[Prototype]]

EventMixin

  1. 现在让我们为实际运用构造一个 mixin
  2. 例如,许多浏览器对象的一个重要功能是它们可以生成事件
    1. 事件是向任何有需要的人“广播信息”的好方法
    2. 因此,让我们构造一个 mixin,使我们能够轻松地将与事件相关的函数添加到任意 class/object
    3. Mixin 将提供 .trigger(name, [...data]) 方法,以在发生重要的事情时“生成一个事件”
      name 参数(arguments)是事件的名称,[...data] 是可选的带有事件数据的其他参数(arguments
    4. 此外还有 .on(name, handler) 方法,它为具有给定名称的事件添加了 handler 函数作为监听器(listener
      当具有给定 name 的事件触发时将调用该方法,并从 .trigger 调用中获取参数(arguments
    5. 还有 .off(name, handler) 方法,它会删除 handler 监听器(listener
    6. 添加完 mixin 后,对象 user 将能够在访客登录时生成事件 "login"
    7. 另一个对象,例如 calendar 可能希望监听此类事件以便为登录的人加载日历

  1. 使用

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享