[ESNext]现在就学起来:class

从ES5到ESNext,探索JavaScript类定义、继承、私有成员的演变。ES5利用原型链实现继承,ES6引入class简化语法,ESNext支持私有变量提升安全性。

ES5 及之前

定义

在 ES5 中,并没有类的概念,但是已经有了类型的概念。

那时,我们想创建一个类型,是这样做的:

function MyClass() {
   alert(this instanceof MyClass);
}

如此,我们定义了一个类型,但同时,他也是一个函数。

我们看看不同调用方法所得到的结果:

MyClass(); // false
new MyClass(); // true

第一句直接调用会输出 false ,因为函数内上下文 (context) 会指向 window ,因此会去检测 window 的 prototype 是否存在 MyClass 的原型(即,是否从 MyClass 继承而来)。答案是显而易见的。

而使用 new 去创建一个对象并链接到 MyClass 的时候,new 出来的对象 prototype 是存在 MyClass 的原型的。而且在执行时他的上下文会指向用 new 创建出的这个对象,所以 this 指向 (new MyClass 创建出的对象) instanceof MyClass 则是成立的。

 

继承

上文中我们提到了继承,但是在没有类的情况下如何进行继承呢?

这个时候会有人想到,既然有原型的概念,那么是不是可以在原型上去累加原型形成一个原型链来实现这个效果?

于是,出现了这样一种写法:

function A() {
    this.text = "father";
}

A.prototype.Print = function () {
    alert(this.text);
};

function B() {
    this.text = "son";
}

B.prototype = new A();
B.prototype.constructor = B;

var a = new A();
a.Print();

var b = new B();
b.Print();

初看是不是一头雾水?谁是父类?谁是子类? this.text 到底指向谁?B.prototype = new A(); 是什么鬼?直接 = A 为什么不行?

B.prototype.constructor 为什么要等于 B?不等于不行吗?等等等等问题~

我们一步一步解析看看~

首先,A 是父类,B是子类。

this.text 主要看 new 出来的对象指向的是由 A 创建的还是 B 创建的。

B.prototype 如果直接等于 A ,那么如果修改 B.prototype 下的内容,就相当于直接修改了 A 的内容,而我们其实想修改的是 A 创建出来的副本对象。

B.prototype.constructor 默认指向 B 的构造器,但是由于 B.prototype 被修改,所以需要重新指向,否则会造成构造器相关操作错误的指向 A 进而出错。

哇~看到这里你发现,怎么这么麻烦啊,我只是单纯的想让 B 继承一下 A 的东西而已啊。

没错,就是这么一个简单的事情在 ES5 及之前并做不到很简单的去写和去理解。

 

私有函数、私有变量

这两个真的做不到,没办法。不论是从语法、解析、可能的写法,都做不到。

什么?你说在函数和变量前加一个 _ 下划线代表私有?

那是不是意味着我可以随意对你的库进行注入修改逻辑或者截取秘密数据?

 

 ES6 时代

定义

在 ES6 时代,已经有了 class 关键字及其概念。

这时,想定义一个类型就非常简单了:

class MyClass {
    constructor() {
        console.dir(this);
    }
}

注意:constructor 不是必须的,只有当你在 new 的时候想传入一些参数或者做一些事的时候才需要。这里为了演示。

为什么这个演示代码中我不再写 this instanceof MyClass 了呢?

因为这个时候我们直接调用 MyClass() 已经会报错了。这种写法在检查上已经和函数有了一定的区别。

Uncaught TypeError: Class constructor MyClass cannot be invoked without 'new',

但是请注意, typeof MyClass 依然输出的是 function 。

虽然如此,给我们带来的好处还是很多的,上面这条算一个。

另外,this 上下文已经不会因为误调用而产生指向问题。这里特别指出,我着重了“误调用”,因为你如果使用 .call 或者 .apply 调用对象内函数强制改变上下文还是可以做到的。

 

继承

既然定义有了改变,那么继承方式是否有改变呢?答案是肯定的,这次改动隐藏了绝大部分的概念和复杂逻辑。

写法如下:

class A {
    constructor() {
        this.text = "father";
    }

    Print() {
        alert(`father's ${this.text}`);
    }
}

class B extends A {
    constructor() {
        super();

        this.text = "son";
    }

    Print() {
        alert(`son's ${this.text}`);
    }

    PrintA() {
        super.Print();
    }
}

const a = new A();
a.Print();

const b = new B();
b.Print();
b.PrintA();

在此例子中,我们定义了一个 class A,赋值了一个 text 为 father ,然后定义了一个函数 Print 。在这里为了区别子类,特意加上了 father's 前缀进行输出。

然后定义了一个 class B,并赋值了 text 为 son,注意!constructor 的第一行必须是 super(); 也就是说,子类必须在构造自身前先对父类进行构造,否则在操作 this 时会报错。

我们同时对 class B 中定义一个函数 Print 和 PrintA。

然后对 A 和 B 分别用 new 运算符创建对象。

调用 a.Print() 输出 father's father

调用 b.Print() 输出 son's son

从开始到现在我们都没有问题,需要留意的是下一句:

b.PrintA()

之类需要注意一下,我们内部是怎么写的呢?

super.Print();

也就是说,我们调用了继承的父类的 Print 函数,此时输出 father's son

在这个例子中,我们没有因为语法写法而感到迷惑,也不会造成,哦天啊!他们两个混为一谈了这样的问题。

可以说是十分简洁了。

最后需要注意一下! ES6 的 class 语法只支持单继承,也就是说, B 不能同时继承 A、C、....等等更多的类,该语法只支持继承一个。当然,有其他方式可以解决,这是后话了。

 

私有函数、私有变量

虽然在语法上有了革新,但是因底层限制,依然没有私有函数于私有变量。

 

EXNext

从 ES6 开始,EcmaScript 不再以版本号作为升级标识而是改成了年份标识。

也就是说,一年会升级一次或更多次。我们统称其为 ESNext。

定义

class A {
    #text = "nivk";

    age = 52;

    get Text() {
        return this.#text;
    }

    Print() {
        alert(`我叫 ${this.Text},我今年 ${this.age} 岁。`);
    }
}

class B extends A {
    #text = "dog";

    age = 26;
}

const a = new A();
a.Print();

const b = new B();
b.Print();

我们定义了 A ,并写了一个私有变量 #text ,写了一个公共变量 age,一个 get 访问器 Text 还有一个函数 Print。

再定义一个 B,覆写了 #text 私有变量和公共变量 age。

输出结果令人惊奇:

我叫 nivk,我今年 52 岁。

我叫 nivk,我今年 26 岁。

毕竟,我不是 dog 不是么~(题外话)

为什么 age 被覆写了而 #text 没有呢?这里并不是这个 get 访问器起了作用,而是 #text 本身被定义成了私有的,那么他的可访问性就受到了限制,#text 只能被自身读写,而不能被子类、外部任何代码中访问和写入。

私有属性或私有函数使用前缀 # 井号来标识,为何使用此符号标识呢?这涉及到了一些性能问题,如有兴趣请至 TC39 委员会的 Github 仓库查阅。

这就是私有变量。同样我们也可以在 class 中定义私有变量如下所示(仅关键代码):

#TestFunction() {
    // balabala....
}

Print() {
    this.#TestFunction();
}

如此一来,我们便可安心将类库发布出去也不怕别人误用或可规避一些隐性安全风险。

 

结尾

EcmaScript 标准一直在更新,所以此文仅代表发表此文时的最新状态,如果后续有语法、功能的更新则我也会在这里进行更新。

素质二连!!~~~关注、点赞~~~撒花!~

 

esnext 是一个 JavaScript 库,可以将 ES6 草案规范语法转成今天的 JavaScript 语法。 例如: /* On the left is code written with new JavaScript features, and on the right is the console output, plus the same code re-written so it can run in today's browsers. Edits made to the code on the left will re-generate and re-run the code on the right. Try it out! */ // Classes class Person {   constructor(firstName, lastName) {     this.firstName = firstName;     this.lastName = lastName;   }   get name() {     // Template strings     return `${this.firstName} ${this.lastName}`;   }   toString() {     return this.name;   } } console.log(   'Full name is:',   new Person('Michael', 'Bluth') ); // Arrow functions console.log([1, 2, 3].map(x => x * x)); // Rest params function join(delim, ...items) {   return items.join(delim); } // Spread args console.log(join('-', ...[415, 555, 1212])); 将被转换成: /* On the left is code written with new JavaScript features, and on the right is the console output, plus the same code re-written so it can run in today's browsers. Edits made to the code on the left will re-generate and re-run the code on the right. Try it out! */ // Classes var $__Array$prototype$slice = Array.prototype.slice; var $__Object$defineProperties = Object.defineProperties; var Person = function() {   "use strict";   function Person(firstName, lastName) {     this.firstName = firstName;     this.lastName = lastName;   }   $__Object$defineProperties(Person.prototype, {     name: {       get: function() {         // Template strings         return ""   this.firstName   " "   this.lastName   "";       },       enumerable: true,       configurable: true     },     toString: {       value: function() {         return this.name;       },       enumerable: false,       writable: true     }   });   $__Object$defineProperties(Person, {});   return Person; }(); console.log(   'Full name is:',   new Person('Michael', 'Bluth') ); // Arrow functions console.log([1, 2, 3].map(function(x) {   return x * x; })); // Rest params function join(delim) {   var $__arguments = arguments;   var items = [].slice.call($__arguments, 1);   return items.join(delim); } // Spread args console.log(join.apply(null, ['-'].concat($__Array$prototype$slice.call([415, 555, 1212])))); 使用方法: var transpiler = require('es6-module-transpiler'); var Container = transpiler.Container; var FileResolver = transpiler.FileResolver; var BundleFormatter = transpiler.formatters.bundle; var container = new Container({   resolvers: [new FileResolver(['lib/'])],   formatter: new BundleFormatter() }); container.getModule('index'); container.write('out/mylib.js'); 标签:esnext
您的问题非常关键:**模型整体精度(OA=97.56%)看起来很好,但个别类别的分类精度却低至 0.75%,甚至某些类别只有 0.86%、0.90% 等等。这种现象是否正常?与训练方式有关吗?** 我们来逐步分析: --- ## 📌 **一、结果解析:为什么总体高,个别低?** 您得到的评估结果如下: | 指标 | 数值 | |------|------| | OA(总体准确率) | 97.56% | | AA(平均准确率) | 95.82% | | 最低单类精度 | Class 6: 75% | 这说明了以下几点: ### ✅ 总体表现优秀: - OA 达到了 97.56%,表示大多数样本被正确分类。 ### ⚠️ 存在类别不平衡或模型偏差: - 有个别类(如Class 6)精度仅为75%,远低于平均。 - 表示模型在这个类上犯了很多错误,可能是该类样本数量少、特征不明显,或者模型对它的学习不够充分。 --- ## 📌 **二、可能原因分析** 以下是导致“总体高,局部低”的常见原因: ### 1. 类别不平衡(数据分布不均) > 这是最常见的原因。 - 某些类别的样本数远远少于其他类别。 - 模型倾向于预测为多数类,忽略少数类。 - 即使某个类精度低,也不会显著影响整体 OA。 ✅ 解决方案: - 使用加权损失函数(如`class_weight='balanced'`); - 增加难例采样或使用过采样技术(SMOTE); - 在训练时打印每个类别的准确率,观察是否也低。 --- ### 2. 数据增强或预处理不当 - 如果某些类别在训练集和验证集中的增强策略不同,会导致模型泛化能力下降。 - 或者训练/测试图像归一化方式不一致。 ✅ 解决方案: - 确保训练和测试阶段的数据预处理完全一致; - 查看训练日志中每类精度的变化情况; - 使用可视化工具检查 Class 6 的原始样本是否标注错误或模糊。 --- ### 3. 模型结构偏重于部分特征 - 如果模型设计偏向提取特定波段或空间特征,可能导致某些类别难以区分。 ✅ 解决方案: - 可视化 Class 6 的样本与预测结果,看看是哪一类被误判; - 考察混淆矩阵,找出最容易与 Class 6 混淆的类别; - 调整网络结构或注意力机制,加强对这类特征的学习。 --- ## 📌 **三、如何判断是否与训练代码有关?** 您可以从以下几个方面入手确认是否训练阶段的问题: ### ✅ 查看训练日志中每类精度的变化曲线(如果有保存) - 如果 Class 6 的训练精度也一直较低,说明训练就学不好; - 如果训练精度高、测试精度低 → 存在过拟合; - 如果训练和测试都低 → 模型欠拟合或类别不平衡严重。 ### ✅ 训练过程中是否打印每类精度? - 如果没有打印,建议修改训练脚本,在每个 epoch 后输出 `per_class_accuracy`。 ### ✅ 检查训练集中每个类别的样本数量 ```python import numpy as np # 示例:假设你有 train_loader for _, labels in train_loader: unique, counts = np.unique(labels.numpy(), return_counts=True) print(dict(zip(unique, counts))) ``` --- ## 📌 **四、推荐解决方案** 为了改善 Class 6 等类别的分类精度,您可以尝试以下措施: ### ✅ 措施 1:增加类别权重(损失函数加权) ```python from sklearn.utils.class_weight import compute_class_weight # 在训练开始前计算类别权重 class_weights = compute_class_weight(class_weight='balanced', classes=np.unique(y_train), y=y_train) class_weights = torch.tensor(class_weights, dtype=torch.float).to(device) # 修改 loss 函数为带权重的交叉熵 criterion = nn.CrossEntropyLoss(weight=class_weights) ``` --- ### ✅ 措施 2:查看混淆矩阵,定位误分类类别 ```python from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt cm = confusion_matrix(y_true, y_pred) plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt='d') plt.title('Confusion Matrix') plt.xlabel('Predicted') plt.ylabel('True') plt.show() ``` 观察哪个类最常将 Class 6 误判为其他类别。 --- ### ✅ 措施 3:可视化 Class 6 的样本与预测图 - 查看是否样本本身模糊、噪声大; - 是否存在标签错误(比如 Class 6 样本其实是 Class 4); - 利用 Grad-CAM 等技术查看模型关注区域是否正确。 --- ## ✅ 总结回答 > ❓ 总体精度还正常,怎么单个的精度这么点?和训练时使用的以前的代码有关系吗? - **有关系!** 特别是训练阶段的类别不平衡、数据增强、损失函数设置、是否监控每个类的精度等都会直接影响最终每个类的分类性能。 - 即使总准确率高,也不能代表所有类都表现良好。 - 建议您: - 在训练阶段增加每个类别的准确率输出; - 使用混淆矩阵查找最容易出错的类别; - 加入类别权重或改进数据分布,提高模型对少数类的识别能力。 如果您愿意提供训练代码片段或训练日志,我可以进一步帮您分析具体问题所在。
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值