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. MYSQL 使用基础 - 这么用就对了

    这篇文章主要梳理了 SQL 的基础用法,会涉及到以下方面内容: SQL大小写的规范 数据库的类型以及适用场景 SELECT 的执行过程 WHERE 使用规范 MySQL 中常见函数 子查询分类 如何选 ...

  2. Python 爬取异步加载的数据

    在我们的工作中,可能会遇到这样的情况:我们需要爬取的数据是通过ajax异步加载的,这样的话通过requests得到的只是一个静态页面,而我们需要的是ajax动态加载的数据! 那我们应该怎么办呢??? ...

  3. 集训作业 洛谷P1032 字串变换

    集训的题目有点多,先写困难的绿题吧(简单的应该想想就会了) 嗯,这个题看起来像个搜索呢(就是个搜索) 我们仔细想想就知道这个题肯定不能用深搜,可以优化的地方太少了,TLE是必然的. 那我们该怎么办呢? ...

  4. JAVA面向对象:三大特征 封装讲解

    一.JAVA封装 1.封装的理解 封装是 JAVA 面向对象思想的 一 种特性,也是一种信息隐蔽的技术 2.封装的原则 将类中的某些信息隐藏起来,来防止外部程序直接访问,通过类中的方法实现对隐藏的信息 ...

  5. 二进制图片blob数据转canvas

    javascript是有操作二进制文件的方法的,在这里就不详述了. 而AJAX的核心XMLHttpRequest也可以获取服务端给的二进制Blob. 可以参考: XMLHttpRequest Leve ...

  6. python常见报错信息!错误和异常!附带处理方法

    作为 Python 初学者,在刚学习 Python 编程时,经常会看到一些报错信息. Python 有两种错误很容易辨认:语法错误和异常. Python assert(断言)用于判断一个表达式,在表达 ...

  7. Redis知识总结

    1.什么是Redis Redis是一个nosql(not only sql 不仅仅只有sql)数据库,翻译成中文叫做非关系型数据库,低由C语言开发,数据模型为key-value 关系型数据库:以二维表 ...

  8. 一个edit的学习笔记

    https://blog.csdn.net/woshizoe/article/details/51555396

  9. Nginx与Apache简单对比

    Nginx 1.轻量级,采用C进行编写,同样的 web 服务,会占用更少的内存及资源 2.抗并发,处理请求是异步非阻塞的,负载能力比apache高很多,而 apache 则是阻塞型的.在高并发下 ng ...

  10. laravel 上线部署最佳实践

    nginx  配置 listen 80 default_server; server_name xxxx; index index.php index.html;    优先 index.php ro ...