您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
前端 |JavaScript基础之类
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
前端 |JavaScript基础之类
京东科技IoT团队
2020-12-30
IP归属:未知
318040浏览
> 类是 ES6 新引入的基础性语法糖结构。其内部的实现依然采用的是原型和构造函数的概念。作用是弥补 ES5 面向对象编程的缺陷以及实现继承代码冗长和混乱的问题。 ### 类是个特殊函数 ``` class Foo{} let foo = new Foo() typeOf Foo // function Foo.prototype // {constructor: f()} Foo.prototype.constructor === Foo // true foo instanceof Foo // true Foo.constructor === Function // true Foo.__proto__.constructor === Function // true Foo.__proto__.__proto__.constructor === Object // true // [class Foo{}] function f(Foo) { return new Foo()} ``` ### 类定义 - 定义类有以下两种方式 ```js // 类声明 class Person {} // 类表达式 const Foo = class {} const Foo = class FooName {} ``` - 类名首字母一般大写,以便区分创建的实例。 - 类的引用受块作用域限制。 - 类的声明没有提升,在声明类前引用类会报错,在类表达式求值前拿到的值是 `undefined` - 类内部代码都在严格模式下执行 ### 类构成 ```js const Foo = class FooName { // 类名为 Foo.name === FooName.name === 'FooName' // A class may only have one constructor constructor(){} // 构造函数,非必需?, 隐式补全 get getProp() {} // 获取函数,非必需 set setProp() {} // 设置函数,非必需 static fun(){} // 静态类方法,非必需, fun() {} // 原型方法,非必需 } ``` #### 类名 name ```js const Foo = class FooName {} Foo.name === 'FooName' // true FooName.name //报错, 在实例方法内可拿到name FooName.name === 'FooName' const Foo = class {} Foo.name === 'Foo' // true ``` #### 实例、原型、类成员 类的语法可定义存在于实例,原型和类本身的成员对象。 ##### 实例成员 - 构造函数内的属性和方法实例化后都是实例上的自有属性,而且每个实例对应唯一的成员对象。 - 实例化过程结束后,仍然可以为实例添加自有属性 ```js const Foo = class FooName { constructor() { this.name = 'zhangsan'; this.sex = 'boy'; this.brother = ['jack', 'tom']; } } let foo = new Foo(); let foo1 = new Foo(); foo.brother === foo1.brother // false foo.age = 20; console.log(foo); // FooName {name: "zhangsan", sex: "boy", brother: Array(2), age: 20} ``` ##### 原型成员 - 类块中定义的所有内容都会定义在类的原型上,可在实例间共享 - 类块中不能直接给原型添加原始值或对象作为成员对象(类块中只能定义方法) - 类块中的方法名可使用字符串、符号或计算的值 - 类块中支持定义获取和设置访问器 ```js const Foo = class FooName { constructor() { this.name='lili'} name: 'tom' // 报错 Unexpected token' getName() { console.log('zhangsan')} ['fun' + 'Name']() {} set name(newName) { this.name_ = newName console.log('设置了' + newName) } get name() { return 'tom' } } const foo = new Foo() Foo.prototype.getName() // zhangsan foo.getName() // zhangsan foo.name = 'jack' console.log(foo.name) // tom ``` ##### 类成员 - 类成员方法通过实例调不到 - 静态方法是唯一个定义在类本身上的方法 - 每个类上只能有一个静态成员 ? - 静态成员中的 this 指向的是类本身 - 静态类方法非常适合作实例工厂 ```js const Foo = class FooName { constructor(age) { this.name = 'zhangsan'; this.age = age; } static create() { return new Foo(Math.floor(Math.random() * 100))} static fu1() {console.log(this)} } const foo = new Foo(); cosnole.log(foo.fu1); // 报错 类成员方法通过实例调不到 console.log(Foo.create()); // FooName {name: "zhangsan", age: ...} Foo.fu1(); // class FooName { ... } ``` #### 非函数原型和类成员 > 类定义中之所以没有显示支持添加**数据成员**,是因为在共享目标上(原型和类)添加可变数据成员是一种反模式。一般来说,对象实例应该独自拥有通过 this 引用的数据。 ```js const Foo = class FooName { sayName() { console.log(Foo.greeting + ' ' + this.name)} } // 在类上手动定义数据成员, 暂且叫做静态属性 Foo.greeting = 'my name is' // 在原型上定义数据成员 Foo.prototype.name = ' zhangsan' const foo = new Foo(); foo.sayName(); // my name is zhangsan ``` #### 迭代器和生成器方法 类定义语法支持在原型和类上定义生成器方法 ```js const Foo = class FooName { constructor() { this.names = [1,2,3] } // 原型上的生成器 *funIterator() { yield '1'; yield '2'; yield '3' } // 定义生成器,把类实例变成可迭代对象 // ???? *[Symbol.iterator]() { yield *this.names.entries() } // 直接返回迭代器实例 [Symbol.iterator]() { return this.names.entries() } // 类上的生成器 static *funIterator() { yield 'a'; yield 'b'; yield 'c' } } let foo = new Foo() let iteratorNum = foo.funIterator(); let iteratorLetter = Foo.funIterator(); iteratorNum.next().value // 1 iteratorNum.next().value // 2 iteratorNum.next().value // 3 iteratorLetter.next().value // a iteratorLetter.next().value // b iteratorLetter.next().value // c for(let[idx, name] of foo) {console.log(name)} // 1, 2, 3 ``` ### 类的实例化 类只能通过 new 操作符实例化,此时,js 解释器知道是要用 new 去调用类的构造函数( constructor)。 ```js const Foo = class {} foo = new Foo new Foo <=> new Foo() foo.constructor() // 报错 const foo1 = new foo.constructor() // 类实例化后,类的构造方法会成为实例的一个普通方法, 而且这个实例也是类的实例 const foo2 = new Foo.constructor() foo2 此时不是类 Foo 的实例, foo instanceof Foo // true ``` ### 类的继承 类原生就很出色的支持了类继承机制,其背后的实现依旧使用的是原型链。 - ES6 单继承使用 extends 关键字 - 类可以继承类和普通的构造函数(可以继承任何拥有[[Construct]]和原型的对象) - 派生类可以通过原型链访问到类和原型上定义的方法,方法内的this可体现调用者是类还是实例 - extends 可以在表达式中使用 - extends 后边可以是一个 JavaScrip 表达式,这个表达式可以在求值类定义时被求值。 ```js class Foo{ getName(name) {console.log(name, this)} static getAge(age){console.log(age, this)} } // 继承类 class F extends Foo{} let foo = new Foo(); let f = new F(); foo.getName('zhangsan'); //zhangsan Foo{} f.getName('zhangsan'); //zhangsan F{} Foo.getAge(18); //18 class Foo{} F.getAge(18); //18 class F{} function Person() {} function getParentClass() { return Foo } // 继承于表达式类 class F extends getParentClass(){} // 继承构造函数 class child extends Parent{} let F = class extends Foo{} // 合法 ``` #### 神奇的 super - super 仅限于在派生类的类构造方法,实例方法和静态方法内调用。 - 在派生类构造方法内通过 super 可以调用父类构造函数,最终将返回的实例赋给 this - 在派生类静态方法内通过 super 可以调用父类静态方法 - 不能单独引用 super 关键字 - super 调用可以手动传参,功父类 constructor 方法使用 - 派生类即使没有定义构造方法,实例化时仍然会调用 super(),并且会传入实例化时的参数 - 派生类显式定义构造方法,则必须调用super()或者返回一个对象 > ES6 给类构造函数和静态方法添加了内部特性 [[HomeObject]] ,其本质是个指针,指向定义该方法的对象。这个指针是自动赋值而且只能在 JavaScript 引擎内部访问。 [[HomeObject]] 的原型就是 super。 ``` class Foo{ constructor() { super() // 报错 this.name = 'zhangsan' } getName(name) {console.log(name)} static getAge(age){console.log(age)} } class F extends Foo { constructor() { // super 调用之前并不能引用this,否则跑出ReferenceError super() // 等同于 super.constructor() console.log(this) // F{name: 'zhangsan'} consolee.log(super) // 报错 } static getAge(name) { super.getAge(name) } } const f = new F(); F.getAge('jack') // jack ``` ### 抽象基类-看着确实很鸡肋 - 可供其他类继承,但不能被实例化, ES6 本身并没支持这个特性 - new.target 保存了通过 new 关键字调用的类或函数 - 在基类中可通过 new.target 判断当前实例化的是不是基类,如果是可抛出异常 - 在基类中也可以约束派生类的行为,比如必须定义某个方法 ``` class Foo { constructor() { console.log(new.target) // class Foo{...} if(new.target === Foo) {throw new Error('基类不能被实例化')} if(!this.getName){throw new Error('派生类F中没有定义方法 getName')} } } class F extends Foo {} new Foo() // Error: 基类不能被实例化 new F() // 派生类F中没有定义方法 getName ``` #### 继承内置类型 - 扩展内置类型(比如Array)相对方便 - 部分内置类型的方法会返回的新实例,默认与原始实例的类型一致 - 通过覆盖 Symbol.species 覆盖默认行为, Symbol.species 决定在创建返回的实例时使用的类 以 Array 为例 ```js class Arr extend Array {} let arr1 = new Arr(1,2,3,4,5) let arr2 = a1..filter(x=>!!(x%2)) console.log(arr1 instanceof Arr) // true console.log(arr2 instanceof Arr) // true class Arr extend Array { static get [Symbol.species]{ return Array} } let arr1 = new Arr(1,2,3,4,5) let arr2 = a1..filter(x=>!!(x%2)) console.log(arr1 instanceof Arr) // true console.log(arr2 instanceof Arr) // false ``` #### 类混入-类实现多继承的伪实现 - Object.assign() 是为了混入对象行为而设计的,对只是混入多个对象属性的场景屡试不爽。 - 通过多层嵌套函数结合类继承的表达式写法实现类的逐层继承 - 通过 reduce 方法,可将嵌套写法拆开,具体例子见 红宝书 V4 page264 > 众所周知,软件设计原则推崇的是组合模式(把方法提取到独立的类和辅助对象中,然后把它们组合起来)。特别是,React 已经抛弃了混入模式 ### 写在最后 ES6 新增的类很大程度上是基于既有原型机制的语法糖。类的语法让开发者们可以优雅的定义向后兼容的类,既可以继承内置类型,也可以继承自定义类型。类有效的跨越了对象实例、对象原型和对象类的鸿沟。 > 作者: 张永强
原创文章,需联系作者,授权转载
上一篇:大数据 | ClickHouse在京东能源管理平台的实践
下一篇:前端 |数据大屏适配方案
京东科技IoT团队
文章数
13
阅读量
110382
作者其他文章
01
前端 | 小程序横竖屏的坑和 rpx 布局方案
如何避免小程序开发过程中的那些“坑”
01
前端 | Chrome 80 中 Iframe cookie 无法携带的问题
Chrome 80 中 Iframe cookie 无法携带的问题求解过程。
01
NLU | 智能音箱语义理解——MDIS三合一模型
MDIS模型(Unified Model for Multiple Domain Intent and Slot)可以做到同时对话术进行领域分类、意图判断以及填槽。
01
前端 |数据大屏适配方案
数据大屏适配方案详解
京东科技IoT团队
文章数
13
阅读量
110382
作者其他文章
01
前端 | 小程序横竖屏的坑和 rpx 布局方案
01
前端 | Chrome 80 中 Iframe cookie 无法携带的问题
01
NLU | 智能音箱语义理解——MDIS三合一模型
01
前端 |数据大屏适配方案
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号