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. 02 flask源码剖析之flask快速使用

    02 flask快速使用 目录 02 flask快速使用 1.flask与django的区别 2. 安装 3. 依赖wsgi Werkzeug 4. 快速使用flask 5. 用户登录&用户管 ...

  2. OSCP Learning Notes - Enumeration(4)

    DNS Enumeration 1. Host Tool host is a simple utility for performing DNS lookups. It is normally use ...

  3. echarts 踩坑 : 为什么触摸柱状图的时后柱子不见了?原来是color的锅!

    今天发现一个奇怪的问题. 当我的鼠标触摸柱状图的时候,柱状图就消失了. 后来发现是颜色的设置有问题. color: ['rgba(68,238,224)', 'rgba(17,215,255)', ' ...

  4. 题解 洛谷 P5324 【[BJOI2019]删数】

    先考虑对于一个序列,能使其可以删空的的修改次数. 首先可以发现,序列的排列顺序是没有影响的,所以可以将所有数放到桶里来处理. 尝试对一个没有经过修改的可以删空的序列来进行删数,一开始删去所有的\(n\ ...

  5. java 多线程的售票问题

    java 多线程的售票问题 对票的库存进行操作 public class Tickets implements Runnable{ private int ticket = 100; public v ...

  6. 理解Linux的硬链接与软链接-转载

    理解Linux的硬链接与软链接 来自:https://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/index.html

  7. vue学习(十) v-for循环普通数组 、对象数组、 迭代数字

    //html <div id="app"> <p v-for="item in list">{{item}}</p> < ...

  8. Java基础之java8新特性(1)Lambda

    一.接口的默认方法.static方法.default方法. 1.接口的默认方法 在Java8之前,Java中接口里面的默认方法都是public abstract 修饰的抽象方法,抽象方法并没有方法实体 ...

  9. C#使用Halcon连接相机

    (注意:一个相机不能两个软件同时使用在使用vs的时候把halcon关掉,用halcon的时候把vs的关掉切记*一个大坑* 在vs中调用的代码的时候要是用多线程才能显示出来图像不然则录像显示不出来) 1 ...

  10. IDEA中项目的两种打包方式

    本文主要介绍在IDEA中怎么打包,及可以用哪种方式打包. 若是有指正或补充的,欢迎留言~  ٩(●̮̃•)۶ 接下来进入正题: IDEA中打包需要先进行配置,so,我们先打开<abbr titl ...