《每天十分钟》-红宝书第4版-对象、类与面向对象编程(七)

2024-04-15 04:12

本文主要是介绍《每天十分钟》-红宝书第4版-对象、类与面向对象编程(七),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

之前的几个继承模式,只使用 ECMAScript 5 的特性来模拟类似于类(class-like)的行为。不难看出,各种策略都有自己的问题,也有相应的妥协,而且代码又丑又长。
为了解决这些问题,ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。类(class)是ECMAScript 中新的基础性语法糖结构,因此刚开始接触时可能会不太习惯。虽然 ECMAScript 6 类表面上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。

类的定义
与函数类型相似,定义类也有两种主要方式:类声明和类表达式。这两种方式都使用 class 关键字加大括号:

// 类声明
class Person {} 
// 类表达式
const Animal = class {};

注意:类不会像变量一样提升
类的构成
包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。

// 空类定义,有效 
class Foo {} 
// 有构造函数的类,有效
class Bar { constructor() {} 
} 
// 有获取函数的类,有效
class Baz { get myBaz() {} 
} 
// 有静态方法的类,有效
class Qux { static myQux() {} 
}

类构造函数
constructor 关键字用于在类定义块内部创建类的构造函数。方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。
类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操作符。而普通构造函数如果不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。调用类构造函数时如果忘了使用 new 则会抛出错误:

function Person() {} 
class Animal {} 
// 把 window 作为 this 来构建实例
let p = Person(); 
let a = Animal(); 
// TypeError: class constructor Animal cannot be invoked without 'new'

把类当成特殊函数
ECMAScript 中没有正式的类这个类型。从各方面来看,ECMAScript 类就是一种特殊函数。声明一个类之后,通过 typeof 操作符检测类标识符,表明它是一个函数:

class Person {} 
console.log(Person); // class Person {} 
console.log(typeof Person); // function

实例、原型和类成员

  1. 实例成员
    每个实例都对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享
class Person { constructor() { // 这个例子先使用对象包装类型定义一个字符串// 为的是在下面测试两个对象的相等性this.name = new String('Jack'); this.sayName = () => console.log(this.name); this.nicknames = ['Jake', 'J-Dog'] } 
} 
let p1 = new Person(), p2 = new Person(); 
p1.sayName(); // Jack 
p2.sayName(); // Jack 
console.log(p1.name === p2.name); // false 
console.log(p1.sayName === p2.sayName); // false 
console.log(p1.nicknames === p2.nicknames); // false 
p1.name = p1.nicknames[0]; 
p2.name = p2.nicknames[1]; 
p1.sayName(); // Jake 
p2.sayName(); // J-Dog
  1. 原型方法与访问器
    为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。
class Person { constructor() { // 添加到 this 的所有内容都会存在于不同的实例上this.locate = () => console.log('instance'); }// 在类块中定义的所有内容都会定义在类的原型上locate() { console.log('prototype'); } 
} 
let p = new Person(); 
p.locate(); // instance 
Person.prototype.locate(); // prototype

可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据:

class Person { name: 'Jake' 
} 
// Uncaught SyntaxError: Unexpected token

类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键:

const symbolKey = Symbol('symbolKey'); 
class Person { stringKey() { console.log('invoked stringKey'); } [symbolKey]() { console.log('invoked symbolKey'); } ['computed' + 'Key']() { console.log('invoked computedKey'); } 
} 
let p = new Person(); 
p.stringKey(); // invoked stringKey 
p[symbolKey](); // invoked symbolKey 
p.computedKey(); // invoked computedKey

类定义也支持获取和设置访问器。语法与行为跟普通对象一样:

class Person { set name(newName) { this.name_ = newName; } get name() { return this.name_; } 
} 
let p = new Person(); 
p.name = 'Jake'; 
console.log(p.name); // Jake
  1. 静态类方法
    可以在类上定义静态方法。这些方法通常用于执行不特定于实例的操作,也不要求存在类的实例。与原型成员类似,静态成员每个类上只能有一个。
    静态类成员在类定义中使用 static 关键字作为前缀。在静态成员中,this 引用类自身。其他所有约定跟原型成员一样:
class Person { constructor() { // 添加到 this 的所有内容都会存在于不同的实例上this.locate = () => console.log('instance', this); } // 定义在类的原型对象上locate() { console.log('prototype', this); } // 定义在类本身上static locate() { console.log('class', this); } 
} 
let p = new Person(); 
p.locate(); // instance, Person {} 
Person.prototype.locate(); // prototype, {constructor: ... } 
Person.locate(); // class, class Person {}

静态类方法非常适合作为实例工厂:

class Person { constructor(age) { this.age_ = age; } sayAge() { console.log(this.age_); } static create() { // 使用随机年龄创建并返回一个 Person 实例return new Person(Math.floor(Math.random()*100)); } 
} 
console.log(Person.create()); // Person { age_: ... }
  1. 非函数原型和类成员
    虽然类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,可以手动添加:
class Person { sayName() { console.log(`${Person.greeting} ${this.name}`); } 
} 
// 在类上定义数据成员
Person.greeting = 'My name is';
// 在原型上定义数据成员
Person.prototype.name = 'Jake'; 
let p = new Person(); 
p.sayName(); // My name is Jake

注意 类定义中之所以没有显式支持添加数据成员,是因为在共享目标(原型和类)上添加可变(可修改)数据成员是一种反模式。一般来说,对象实例应该独自拥有通过 this引用的数据。

  1. 迭代器与生成器方法
    类定义语法支持在原型和类本身上定义生成器方法:
class Person { // 在原型上定义生成器方法*createNicknameIterator() { yield 'Jack'; yield 'Jake'; yield 'J-Dog'; } // 在类上定义生成器方法static *createJobIterator() { yield 'Butcher'; yield 'Baker'; yield 'Candlestick maker'; } 
} 
let jobIter = Person.createJobIterator(); 
console.log(jobIter.next().value); // Butcher 
console.log(jobIter.next().value); // Baker 
console.log(jobIter.next().value); // Candlestick maker 
let p = new Person(); 
let nicknameIter = p.createNicknameIterator(); 
console.log(nicknameIter.next().value); // Jack 
console.log(nicknameIter.next().value); // Jake 
console.log(nicknameIter.next().value); // J-Dog

因为支持生成器方法,所以可以通过添加一个默认的迭代器,把类实例变成可迭代对象:

class Person { constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } *[Symbol.iterator]() { yield *this.nicknames.entries(); } 
} 
let p = new Person(); 
for (let [idx, nickname] of p) { console.log(nickname); 
}
// Jack 
// Jake 
// J-Dog

也可以只返回迭代器实例:

class Person { constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } [Symbol.iterator]() { return this.nicknames.entries(); } 
} 
let p = new Person(); 
for (let [idx, nickname] of p) { console.log(nickname); 
} 
// Jack 
// Jake 
// J-Dog

最近又有点偷懒,罪过罪过,下接抄继承
最近带娃徒步了几座古道,古道上很少商业化,这可能也是徒步古道的重要原因,背上点露营餐拿上登山杖,一路攀登,眼睛看到的是山的原貌,耳朵听到的是各种鸟的叫声,美~,欣赏一首
醉眠 唐庚·宋
山静似太古,日长如小年。
馀花犹可醉,好鸟不妨眠。
世味门常掩,时光簟已变。
梦中频得句,捻笔又忘筌。

这篇关于《每天十分钟》-红宝书第4版-对象、类与面向对象编程(七)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/904824

相关文章

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

Python打印对象所有属性和值的方法小结

《Python打印对象所有属性和值的方法小结》在Python开发过程中,调试代码时经常需要查看对象的当前状态,也就是对象的所有属性和对应的值,然而,Python并没有像PHP的print_r那样直接提... 目录python中打印对象所有属性和值的方法实现步骤1. 使用vars()和pprint()2. 使

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

Spring中管理bean对象的方式(专业级说明)

《Spring中管理bean对象的方式(专业级说明)》在Spring框架中,Bean的管理是核心功能,主要通过IoC(控制反转)容器实现,下面给大家介绍Spring中管理bean对象的方式,感兴趣的朋... 目录1.Bean的声明与注册1.1 基于XML配置1.2 基于注解(主流方式)1.3 基于Java

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

golang 对象池sync.Pool的实现

《golang对象池sync.Pool的实现》:本文主要介绍golang对象池sync.Pool的实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收的压力,下面就来介绍一下,感兴趣的可以了解... 目录sync.Pool的用法原理sync.Pool 的使用示例sync.Pool 的使用场景注意sync.

SpringBoot项目中Redis存储Session对象序列化处理

《SpringBoot项目中Redis存储Session对象序列化处理》在SpringBoot项目中使用Redis存储Session时,对象的序列化和反序列化是关键步骤,下面我们就来讲讲如何在Spri... 目录一、为什么需要序列化处理二、Spring Boot 集成 Redis 存储 Session2.1

Java实例化对象的​7种方式详解

《Java实例化对象的​7种方式详解》在Java中,实例化对象的方式有多种,具体取决于场景需求和设计模式,本文整理了7种常用的方法,文中的示例代码讲解详细,有需要的可以了解下... 目录1. ​new 关键字(直接构造)​2. ​反射(Reflection)​​3. ​克隆(Clone)​​4. ​反序列化