JavaScript calss语法糖

基础知识

  严格意义上来讲,在Js中是没有类这一概念的。

  我们可以运用前面章节提到的构造函数来模拟出类这一概念,并且可以通过原型对象的继承来完美的实现实例对象方法复用。

  但是这样十分的麻烦,我们需要将实例对象需要用到的公共方法来存放到构造函数的原型对象中,而使用class语法糖整个过程就变得非常简单。

声明定义


  使用class来定义类,类中定义的函数称为方法,不需要用关键字function也不需要用逗号进行分割。

<script>

"use strict";

class User {

f1() {
console.log("运行了f1...");
}
// 不需要逗号分隔
f2() {
console.log("运行了f2...");
}

}

let u1 = new User();

u1.f1(); // 方法调用
u1.f2(); // 方法调用

</script>

构造函数


  使用 constructor 构造函数传递参数,该函数会在new时自动执行。

<script>

"use strict";

class User {

constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}

show(){
console.log(`姓名:${this.name},年龄:${this.age},性别:${this.gender}`);
}

}

let u1 = new User("云崖",18,"男");

u1.show(); // 执行方法

</script>

  构造函数不是必须定义的,当没有定义构造函数时。

  它会自动去查找原型链,相当于如下代码所示。

constructor(...args) {
super(...args);
}

原理分析


  不管是使用构造函数还是class语法糖,其原理都是一样的。

  但是构造函数中的方法应该存放进原型对象,这一步需要我们手动去做,使用class语法糖的结构后则会自动将方法放入原型对象。

<script>

"use strict";

class U1 {
constructor(name) {
this.name = name;
}
show() {
console.log("U1 show");
}
} console.log(typeof U1); // function class只是语法糖,内部还是函数。

</script>
<script>

"use strict";

class U1 {
constructor(name) {
this.name = name;
}
show() {
console.log("U1 show");
}
}

let u1 = new U1("class语法糖");

console.dir(u1);

// ============ 两种操作一模一样 class自动将方法写入原型对象中

function U2(name) {
this.name = name;
}

U2.prototype.show = function () {
console.log("U2 show");
}

let u2 = new U2("构造函数");

console.dir(u2);

</script>

遍历差异


  虽说class定义的类归根结底还是函数,但是与我们手动创建构造函数还是有一些优化措施的。

  class 中定义的方法不能被遍历出来,而构造函数原型对象中的方法却可以被遍历出来。

<script>

"use strict";

class U1 {
constructor(name) {
this.name = name;
}
show() {
console.log("U1 show");
}
}

let u1 = new U1("class语法糖");

for (let key in u1) {
console.log(key); // name Ps:show不会被遍历出来
}


// ============ 两种操作一模一样 class自动将方法写入原型对象中

function U2(name) {
this.name = name;
}

U2.prototype.show = function () {
console.log("U2 show");
}

let u2 = new U2("构造函数");

for (let key in u2) {
console.log(key); // name show
}


</script>

严格模式


  在class中, 默认使用strict 严格模式执行

<script>

// "use strict"; 取消严格模式

class U1 {
constructor(name) {
this.name = name;
}
show() {

(function () {
console.log(this); // class中用严格模式执行代码,所以this指向为undefined
}())

}
}

let u1 = new U1("class语法糖");

u1.show();


// ============ 两种操作一模一样 class自动将方法写入原型对象中

function U2(name) {
this.name = name;
}

U2.prototype.show = function () {

(function () {
console.log(this); // 构造函数中方法中的函数this非严格模式下指向为window
}())

}

let u2 = new U2("构造函数");

u2.show();

</script>

静态访问

静态属性


  静态属性即为类自己单独设置属性,而不是为生成的对象设置,请使用static进行声明。

<script>

"use strict";

class User {

static username = "用户类"; // 如果不加 static,将会变成实例属性

constructor(username) {

this.username = username;
}

}

let u1 = new User("云崖");


console.log(u1.username); // 云崖
console.log(User.username); // 用户类

</script>

  实现原理也非常简单。

<script>

    "use strict";

    function User(username) {

        this.username = username;

    }

    User.username = "用户类";

    let u1 = new User("云崖");

    console.log(u1.username);  // 云崖
console.log(User.username); // 用户类 </script>

静态方法


  静态方法即为类自己单独设置方法,而不是为生成的对象设置,请使用static进行声明。

<script>

    "use strict";

    class User {

        static show(){
console.log("类的方法...");
} } let u1 = new User(); User.show(); u1.show(); // 抛出异常 </script>

  实现原理也非常简单。

<script>

    "use strict";

    function User() { }  // 构造函数

    User.show = function () { console.log( "类的方法..."); };

    let u1 = new User();

    User.show();

    u1.show();  // 抛出异常

</script>

访问器

  使用访问器可以对对象的属性进行访问控制,下面是使用访问器对私有属性进行管理。

语法介绍


  使用访问器可以管控属性,有效的防止属性随意修改

  访问器就是在函数前加上 get/set修饰,操作属性时不需要加函数的扩号,直接用函数名

<script>

    "use strict";

    class User {

        constructor(username){
this.username = username;
} get name(){ // 访问name时返回username return this.username;
} set name(value){ // 设置name其实是给username做设置
this.username = value;
} } let u1 = new User("云崖"); console.log(u1.name); </script>

属性保护


  当外部尝试修改某一属性时,可以使用访问器来进行验证。

<script>

    "use strict";

    class User {

        constructor(username){
this.username = username;
} get name(){ // 访问name时返回username return this.username;
} set name(value){ // 设置name其实是给username做设置 if(typeof value == String){
this.username = value;
} throw Error("value type error,must string");
} } let u1 = new User("云崖"); u1.name = 18; // value type error,must string console.log(u1.name); </script>

私有封装

  私有封装是指内部可以任意调用,外部只能通过访问的接口才能进行调用。

public


  public 指不受保护的属性,在类的内部与外部都可以访问到

<script>

    "use strict";

    class User {

        constructor(username){
this.username = username;
} show(){
return this.username; // 内部可以访问
} } let u1 = new User("云崖"); console.log(u1.username); // 外部也可以访问 console.log(u1.show()); </script>

protected


  protected是受保护的属性修释,不允许外部直接操作,但可以继承后在类内部访问,下面将介绍protected三种封装方法。

命名保护

  将属性定义为以 _ 开始,来告诉使用者这是一个私有属性,请不要在外部使用。

  外部修改私有属性时可以使用访问器 setter 操作

  但这只是提示,就像吸烟时烟盒上的吸烟有害健康,但还是可以抽

<script>

    "use strict";

    class User {

        constructor(username){
this._username = username;
} show(){
return this._username; // 内部可以访问
} } let u1 = new User("云崖"); // console.log(u1._username); // 外部也可以访问,但是如果你是专业人员看到 _开头就知道不该在外部拿他。 console.log(u1.show()); </script>

Symbol

  由于我们的代码都是在一个模块中进行封装的,所以使用Symbol()来进行私有封装非常方便。

  除非使用者打开源代码找到变量名key,否则他只能通过提供的接口来拿到数据。

<script>

    "use strict";

    let key = Symbol();

    class User {

        constructor(username){
this[key] ={username};
} show(){
return this[key].username; // 内部可以访问
} } let u1 = new User("云崖"); console.log(u1); // { Symbol(): {username: "云崖"}} 外部拿不到。只能通过接口来拿
console.log(u1.show()); </script>

WeakMap

  WeakMap是一组键/值对的集,下面利用WeakMap类型特性定义私有属性

<script>

    "use strict";

    let key = new WeakMap();

    class User {

        constructor(username) {
key.set(this, { // 以当前对象作为键来存储。
username,
})
} show() {
return key.get(this).username; // 内部可以访问
} } let u1 = new User("云崖"); console.log(u1); // {} 干干净净,啥都拿不到。
console.log(u1.show()); // 云崖 </script>

private


  private 指绝对私有属性,只在当前类可以访问到,并且不允许继承的子类使用

  为属性或方法名前加 # 为声明为私有属性

  私有属性只能在声明的类中使用

<script>

    "use strict";

    class User {

        #username;  // 对于实例的私有属性来说,必须先定义一下。方法则不用。

        constructor(username) {
this.#check(username);
this.#username = username;
} show() {
return this.#username; // 内部可以访问
} #check = (username) => { // 验证类的私有方法。 私有方法格式必须如此。
if (typeof username != "string") {
throw Error("type error,value type must string.")
}
} } let u1 = new User("云崖"); console.log(u1); // {#username: "云崖", #check: ƒ} console.log(u1.show()); // 只能通过接口访问 // console.log(u1.#check); // 外部不能访问,抛出异常。 // console.log(u1.#username); // 外部不可以访问,抛出异常 </script>

继承特性

  class内部也是采用原型继承,这与构造函数如出一辙。

  如果你不了解原型继承,那么可以看一下我前面的一篇文章,解释的非常清楚了。

  这里就是介绍一些class语法糖如何使用继承。

extends


  以下示例将展示使用extends语法实现原型继承。

  需要注意的是,只要继承的子类中有构造函数,就一定要使用super方法。

  没使用super引发异常

<script>

    "use strict";

    class A {
constructor(name) {
this.name = name;
}
show() {
console.log(`${this.name}运行A类的show...`);
}
} class B extends A { // 继承A的原型对象
constructor(name) { this.name = name; // Uncaught ReferenceError:
// Must call super constructor in derived
// class before accessing 'this' or returning from derived constructor // 意思是必须用super }
} let b1 = new B("实例b1"); b1.show(); </script>

  正确示范

<script>

    "use strict";

    class A {
constructor(name) {
this.name = name;
}
show() {
console.log(`${this.name}运行A类的show...`);
}
} class B extends A { // 继承A的原型对象
constructor(name) { super(name); // 必须使用super,这里用父类的构造函数进行构造。 }
} let b1 = new B("实例b1"); b1.show(); // 实例b1运行A类的show... </script>

  原理如下

<script>

    "use strict";

    function A(name) {
this.name = name;
} A.prototype = { constructor: A, show() {
console.log(`${this.name}运行A类的show...`);
},
}; function B(name){
A.call(this,name);
} Object.setPrototypeOf(B.prototype,A.prototype); // B原型对象继承与A原型对象 let b1 = new B("实例b1"); b1.show(); // 实例b1运行A类的show... </script>

super


  表示从当前原型中执行方法。

  super 一直指向当前对象

  在构造函数中,super一定要放在this声明的前面

  super 只能在类或对象的方法中使用,而不能在函数中使用,换而言之,不要使用function来表示这是一个函数!

  正常调用构造函数示范

<script>

    "use strict";

    class User {

        constructor(name) {
this.name = name;
} show() {
console.log(`姓名:${this.name},年龄:${this.age},性别:${this.gender}`); // this指向始终为当前对象
}
} class Admin extends User { // 继承User的原型对象 constructor(name,age,gender) { super(name); // 使用父类的构造函数进行构造name,其他参数由自身构造。必须放在this上面 this.age = age;
this.gender = gender; } func() {
super.show(); // 使用super调用的同时会将当前对象this进行传递。
}
} let a1 = new Admin("云崖",18,"男"); a1.func(); // 姓名:云崖,年龄:18,性别:男 </script>

  super调用其他方法的示范

<script>

    "use strict";

    let obj_1 = {
show(){
console.log("执行了show...");
},
}; let obj_2 = { __proto__:obj_1, run(){ super.show(); // 正常执行 }, }; obj_2.run(); // 执行了show... </script>

  错误示范,不能用function进行声明

<script>

    "use strict";

    let obj_1 = {
show() {
console.log("执行了show...");
},
}; let obj_2 = { __proto__: obj_1, run = function () { super.show(); // 'super' keyword unexpected here }, }; obj_2.run(); // 异常,super不能在function中执行 </script>

静态继承


  类自身的属性以及方法都能被继承下来。

<script>

    "use strict";

    class A {

        static description = "类A";

        static show(){
return ("类A的方法");
} } class B extends A { // 继承A的原型对象 } console.log(B.description); // 类A
console.log(B.show()); // 类A的方法 </script>

方法覆写


  当父类原型对象中有一个方法与子类原型对象中方法同名,子类的实例对象将调用子类的原型对象中的方法。

  这是由原型链属性顺序查找引起的。

<script>

    "use strict";

    class A {

        show() {
console.log("A-->show");
} } class B extends A { // 继承A的原型对象 show() {
console.log("B-->show");
} } let b1 = new B(); b1.show() // B-->show </script>

MixIn机制


  由于Js不支持多继承,所以想添加功能必须在某一个原型对象上不断的增加功能,这势必会让其本来的原型显得混乱不堪。

  这种时候就可以使用Mixin机制来实现。

  注意:Minin类应该当做工具箱来使用,而不应该作为其他类的父类被继承下来。

<script>

    "use strict";

    class Vehicle {
// 交通工具类
constructor(name) {
this.name = name;
} whistle() {
console.log(`${this.name}在鸣笛`); // 公用方法放父类中
}
} class Aircraft extends Vehicle {
constructor(name) {
super(name);
}
} class Car extends Vehicle {
constructor(name) {
super(name);
}
} let Flyable_Mixin = {
// 飞行器的功能
fly() {
console.log(`${this.name}在飞`);
},
outer() {
console.log("其他功能...");
},
}; Object.assign(Aircraft.prototype, Flyable_Mixin); //给飞机添加上飞机Mixin的功能 let Car_Mixin = {
// 汽车的功能 reversing() {
console.log(`${this.name}正在倒车入库`);
},
outer() {
console.log("其他功能...");
},
}; Object.assign(Car.prototype, Car_Mixin); //给汽车添加汽车Mixin的功能 let c1 = new Car("法拉利");
let a1 = new Aircraft("波音747"); c1.whistle(); // 法拉利在鸣笛
c1.reversing(); // 法拉利正在倒车入库 a1.whistle(); // 波音747在鸣笛
a1.fly(); // 波音747在飞 </script>

代码示例

原型检测

检测方法


  使用instanceof检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

  使用isPrototypeOf检测一个对象是否是另一个对象的原型链中

<script>

    "use strict";

    class User {

    } 

    let u1 = new User();

    console.log(u1 instanceof User);  // true  u1的原型链中包含User的原型对象吗?

    console.log(User.prototype.isPrototypeOf(u1)); // true User的原型对象在u1的原型链中吗?

</script>

instanceof原理


  递归不断向上检测原型对象。

    function checkPrototype(obj, constructor) {
if (!obj.__proto__) return false;
if (obj.__proto__ == constructor.prototype) return true;
return checkPrototype(obj.__proto__, constructor);
}

JavaScript calss语法糖的更多相关文章

  1. RoR unobtrusive scripting adapter--UJS(一些Javascript的语法糖)

    Learn how the new Rails UJS library works and compares with the old version of jquery_ujs that it re ...

  2. JavaScript中if语句优化和部分语法糖小技巧推荐

    前言 在前端日常开发过程中,if else判断语句使用的次数应该是比较频繁的了,一些较为复杂的场景,可能会用到很多判断,在某个代码块使用很多if else时,代码会显得较为冗余,阅读起来不够清晰. 除 ...

  3. javascript语法糖

    语法糖(Syntactic sugar),也译为糖衣语法 指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用. 通常来说使用语法糖能够增加程序的可读性,从而减少程序代码 ...

  4. javascript异步编程之generator(生成器函数)与asnyc/await语法糖

    Generator 异步方案 相比于传统回调函数的方式处理异步调用,Promise最大的优势就是可以链式调用解决回调嵌套的问题.但是这样写依然会有大量的回调函数,虽然他们之间没有嵌套,但是还是没有达到 ...

  5. 浅析java中的语法糖

    概述 编译器是一种计算机程序, 它主要的目的是将便于人编写.阅读.维护的高级计算机语言所写的源代码程序, 翻译为计算机能解读.运行的低阶机器语言的程序, 即可执行文件.而 javac 就是java语言 ...

  6. 详解es6 class语法糖中constructor方法和super的作用

    大多数面向对象的编程语言都支持类和类继承的特性,而JS却不支持这些特性,只能通过其他方法定义并关联多个相似的对象,这种状态一直延续到了ES5.由于类似的库层出不穷,最终还是在ECMAScript 6中 ...

  7. ES6深入浅出-3 三个点运算 & 新版字符串-1.函数与对象的语法糖

    主要讲的内容 时间充裕的话就讲,模板字面量 默认参数值 首先讲es6之前,我们是怎么做的.例如我们要写一个求和的函数, 请两个参数的和,但是如果有的人就是穿一个参数呢? 那么b没有传值,b的值是多少呢 ...

  8. vue组件化之模板优化及注册组件语法糖

    vue组件化之模板优化及注册组件语法糖 vue组件化 模板 优化  在 https://www.cnblogs.com/singledogpro/p/12054895.html 这里我们对vue.js ...

  9. ES6 class 语法糖不能直接定义原型上的属性

    今天注意到两个东西: 1.为了模拟面向对象,JavaScript的class语法糖屏蔽了原型的概念 class A{ a = 1   // 注意!!这里定义的不是在prototype上的属性,而是给实 ...

随机推荐

  1. css:实现文本两行或多行文本溢出显示省略号

    div{ display: -webkit-box; -webkit-box-orient: vertical; word-break: break-all; word-wrap: break-wor ...

  2. 从安全的角度看待DNS

    以前对DNS(Domain Name System)认识就大概的知道是一个提供域名解析服务,作为互联网的基础设施,任何一个IT人员都会或多或少都接触到DNS,随着我最近的接触不断提高,我发现DNS还是 ...

  3. 如何证明sleep不释放锁,而wait释放锁?

    wait 加锁示例 public class WaitDemo { private static Object locker = new Object(); public static void ma ...

  4. [spring cloud] -- 核心篇

    核心功能: 分布式/版本化配置 服务注册合发现 服务路由 服务和服务之间的调用 负载均衡 断路器 分布式消息传递 ...... 技术体系 组件 服务注册与发现组件:Eureka.Zookeeper和C ...

  5. Mybatis——Mapper解析

    Mapper的注册入口在Configuration的addMapper方法中,其会调用MapperRegistry的addMapper方法. Mapper的注册过程分为两个步骤: 1.创建Mapper ...

  6. vue学习(五) 访问vue内部元素或者方法

    //html <div id="app"> <input type="button" value="ok" v-bind: ...

  7. 【新生学习】深度学习与 PyTorch 实战课程大纲

    各位20级新同学好,我安排的课程没有教材,只有一些视频.论文和代码.大家可以看看大纲,感兴趣的同学参加即可.因为是第一次开课,大纲和进度会随时调整,同学们可以随时关注.初步计划每周两章,一个半月完成课 ...

  8. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

  9. PHP array_fill_keys() 函数

    ------------恢复内容开始------------ 实例 用给定的指定键名的键值填充数组: <?php$keys=array("a","b",& ...

  10. Python 字典(Dictionary) str()方法

    Python 字典(Dictionary) str()方法 描述 Python 字典(Dictionary) str() 函数将值转化为适于人阅读的形式,以可打印的字符串表示.高佣联盟 www.cge ...