feat(更新文章): javaScript 的类

javaScript 的类的详细操作
This commit is contained in:
DefectingCat
2020-12-30 11:23:44 +08:00
parent de59640a4b
commit 784eda3a9f
30 changed files with 783 additions and 353 deletions

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": []
}

View File

@ -141,14 +141,7 @@ let client = function () {
} }
return { return {
engine: engine, engine: engine
whatEngine() {
for (let i in engine) {
if (engine[i]) {
return i;
}
}
}
} }
``` ```
@ -164,13 +157,25 @@ let client = function () {
``` ```
第二步就是WebKit了WebKit需要我们通过判断ua字符串内的特定内容来识别它。在客户端获取UA最好的办法就是通过`navigator.userAgent`属性。 第二步就是WebKit了WebKit需要我们通过判断ua字符串内的特定内容来识别它。在客户端获取UA最好的办法就是通过`navigator.userAgent`属性。
```js ```js
let ua = navigator.userAgent; let ua = navigator.userAgent;
let reg = /AppleWebKit\/(\S+)/; let webkit = /AppleWebKit\/(\S+)/;
if (reg.test(ua)) { if (webkit.test(ua)) {
engine.ver = ua.match(reg)[1]; engine.ver = ua.match(webkit)[1];
engine.webkit = parseFloat(engine.ver); engine.webkit = parseFloat(engine.ver);
} }
```
KHTML 的用户代理字符串中也包含 Gecko因此在排除 KHTML 之前,无法准确检测基于 Gecko 的浏览器。
```js
let khtml = /KHTML\/(\S+)/;
let khtml1 = /Konqueror\/([^;]+)/;
if (khtml.test(ua) || khtml1.test(ua)) {
engine.ver = ua.match(khtml)[1];
engine.khtml = parseFloat(engine.ver);
}
``` ```

View File

@ -0,0 +1,229 @@
<!--
* @Author: your name
* @Date: 2020-12-28 09:04:40
* @LastEditTime: 2020-12-29 19:17:03
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \blog\source\_md\构造函数与绑定this.md
-->
## 丢失this
this 是整个 JavaScript 语言里最令人头疼的特性。在 JavaScript 中this 是动态的也就是说它在运行时是变化的。也正因这一特性this 的变化难以预料,不经意间就会发生令人意外的结果。
先来看一个最基本的例子。我们有一个很智能的 test 函数,它有一个 age 属性和一个 sayAge 方法:
```js
let test = {
age: 18,
sayAge: function () {
console.log(`hi, ${this.age}`);
}
}
```
在正常情况下sayAge 方法里的 this 应该正确指向这个 test 对象:
```js
test.sayAge()
// 18
```
但是在某些不正常的情况下,例如`setTimeout()`方法,由于它的特殊性,会导致 this 的指向不正确。
```js
setTimeout(test.sayAge, 0);
// hi, undefined
```
这时候的 this 就会丢失对 test 对象的链接。这是因为`setTimeout()`调用的代码运行在与所在函数完全分离的执行环境上,它只获取到了函数体,而函数和 test 对象分开了。这就会导致 this 指向了全局对象。
### 如何解决
1. 使用一个包装函数;
2. 使用`bind()`方法。
其他也有很多情况会导致 this 的丢失,包括`bind()`方法都不是本篇主要研究的。我们主要来看下使用函数包装的方法。
使用一个函数包装下的方法非常简单:使用一个匿名的函数作为`setTimeout()`异步函数,在匿名函数内就将对象的方法执行。这样,无论`setTimeout()`的异步函数是在什么环境下执行的都能获取到正确的值了,因为它已经执行过了。
```js
setTimeout(() => { test.sayAge() }, 0);
// hi, 18
```
不过这种方法有个缺点,那就是在`setTimeout()`延迟期间,如果 test 对象的属性值有变动,那么`setTimeout()`就不能输出最新的值。所以`bind()`方法是更好的解决方案。
## 构造函数
还有一种情况就是构造函数,构造函数也是用来创建对象的,而它也能够直接添加一个带有 this 的方法。创建后的对象实例也有同样的问题。但是构造函数有更加优雅的解决方案。
### 箭头函数
往往看到箭头函数想到的就是它非常适合匿名函数,并且没有那么烦人的 this 。
箭头函数只是没有自己的 this它会将 this 当作一个普通变量,向**上层作用域去搜索**。也就是说,箭头函数是继承作用域中的 this 的。这就解释了这里的箭头函数为什么能够正确找到 this。
举个例子,我们都知道当正常情况下闭包的返回会丢失对象的 this
```js
let test = {
age: 18,
sayAge: function () {
return function () {
console.log(this.age);
}
}
}
```
这里的`test.sayAge()()`必然不能正确的找到 this 的指向。而如果我们将 this 当作一个变量,将其的值传递给一个封闭的变量 that闭包就能正常工作啦
```js
let test = {
age: 18,
sayAge: function () {
let that = this
return function () {
console.log(that.age);
}
}
}
```
这样返回`test.sayAge()()`便正常的找到 this 的值。
而我们的箭头函数就是没有自己的 this它只会把 this 当作一个变量在作用域中寻找。所以在闭包里用箭头函数也能正确找到 this
```js
let test = {
age: 18,
sayAge: function () {
return () => {
console.log(this.age); // this 当作变量在上层作用域中找到啦
}
}
}
test.sayAge()() // 18
```
我们的 class 也是同理,`sayAge = ()=>{console.log(this.age);}`是在作用域中找到正确的this指向的。
同样的,对于`setTimeout`方法,闭包的方式也能解决,因为它和包装一层同理,并且解决了延迟的问题。
### 构造函数的“优雅”
首先我们请来一位构造函数:
```js
function Test(age) {
this.age = age;
this.sayAge = function () {
console.log(`hi, ${this.age}`);
}
}
```
这是一个很常见的构造函数,他在内部为每个实例创建了一个方法。这个方法是常规的一个函数,所以基于这个构造函数所创建的实例也会遇到同样的丢失 this 的问题。
根据上述的箭头函数的示例只要在作用域链中使用箭头函数this 就能在作用域链上层被轻松找到,并正确指向。
```js
function Test(age) {
this.age = age;
this.sayAge = () => {
console.log(`hi, ${this.age}`);
}
}
```
因为构造函数本身就是闭包的一种体现(实例方法得在构造函数内创建),回顾下构造函数的 [内部原理](https://www.defectink.com/defect/javascript-object-oriented-programming.html#%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86),一个构造函数在创建实例的时候,会在函数内部隐式的声明一个 this 对象,有了 this 这个对象之后,函数的作用域赋给新对象(所以 this 指向了这个对象)。最后再隐式的 return 出这个对象给实例。
```js
function Make() {
// this = {
// name : 'xfy'
// };
this.name = 'xfy';
// return this;
}
```
构造函数内的方法都以闭包的方式 return 到实例上了,所以在**构造函数内部**给实例所创建的方法(箭头函数)根据闭包的原理,在作用域链中继承了 this所以在实例中使用这个方法时this 不会那么轻松的丢失。
但是,构造函数真的足够“优雅”吗?
当然不够,[构造函数的问题](https://www.defectink.com/defect/javascript-object-oriented-programming.html#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E7%9A%84%E9%97%AE%E9%A2%98) 就在于不能在构造函数内部为实例创建方法。以这种方式创建函数会导致不同的作用域链和标识解析符。但创建Function新实例的机制任然是相同的。所以导致由构造函数创建的实例的方法只是同名而不相等。也就是说它会为每个实例都创建一个同名而不相同的方法
```js
let one = new Test(18);
let two = new Test(18);
console.log(one.sayAge === two.sayAge); // false
```
于是在 ECMAScript 2015 之前,我们都是将实例的方法创建在构造函数的 prototype 对象上:
```js
Test.prototype.sayAge = function () {
console.log(`hi, ${this.age}`);
}
```
但是这样所创建的方法就无法继承构造函数内部的作用域链了。
### class 的优雅
`class`关键字为实例创建的方法都是写在`class`的大括号内的,当然这只是个写法。
```js
class Test {
constructor(age) {
this.age = age;
}
sayAge() {
console.log(`hi, ${this.age}`);
}
}
let one = new Test(18);
let two = new Test(18);
console.log(one.sayAge === two.sayAge); // true
```
它为实例所创建的方法都是完全相同的,也就是说这个方法是在我们类的 prototype 这个对象上的,事实也确实如此。
而当我们在类的内部创建一个箭头函数时,它便不会在 prototype 这个对象上创建方法,反而是和传统构造函数一样。
```js
class Test {
constructor(age) {
this.age = age;
}
sayAge = () => {
console.log(`hi, ${this.age}`);
}
}
let one = new Test(18);
let two = new Test(18);
console.log(one.sayAge === two.sayAge); // false
```
~~某咸鱼起初还以为 class 可以既能在 prototype 对象上创建方法,也能继承作用域链~~
## 总结
* 包装函数是一个不完美的解决 this 丢失的方法;
* 箭头函数没有自身的 this它会在作用域链中搜索
* 闭包出一个箭头函数也可以解决`setTimeout`导致的 this 丢失问题;
* 构造函数不够优雅;
* 在 class 中创建一个箭头函数的方法时,它就不会在 prototype 对象上创建这个方法了;
所以 class 也不够优雅。
## 推荐
* [Class 字段](https://zh.javascript.info/class#class-zi-duan)

View File

@ -1,262 +0,0 @@
* [](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes)
在JS中所谓的类不过是ECMAScript 2015为其引入的语法糖。这个糖它只有甜味它是构造函数的另一种写法类语法**不会**为JavaScript引入新的面向对象的继承模型。
在之前学习[JS面向对象](https://www.defectink.com/defect/javascript-object-oriented-programming.html)的编程时详细的研究过了关于JS构造函数以及继承的问题。从工厂模式一直发展至今的寄生式继承也解决了很多语言本有的问题。虽然类只是个语法糖但是从很多地方来说它也解决了关于构造函数继承等的复杂写法。
## 📍定义类
类实际上是一个特殊的函数,将像函数能够定义的函数表达式和函数声明一样,类语法有两个部分组成:类表达式和类声明。
### 声明
定义一个类的方法是使用一个类声明,要声明一个类,可以使用带有`class`关键字的类名。一个类看上去像这样:
```js
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
otherFunc() {
console.log(name + age);
}
}
```
这和一些常见的语言例如C艹和JAVA较为类似。相比较之下传统的JS构造函数对于其他的面向对象语言的程序员可能不太容易理解来回顾下传统的构造函数。
```js
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.otherFunc() {
console.log(name + age);
}
```
相较于最传统的构造函数来说,类的声明方式会更让人容易理解。虽然声明看上去只是换了种写法(确实就是换了种写法,包括继承),但是对于继承等操作来说,`class`类的方式省了不少事。
### 提升
函数声明和类声明之间的一个重要区别是函数声明会提升,而类声明不会。和新关键字`let``const`等一样,类需要先声明再使用。
```js
let xfy = new Person(); // ReferenceError
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
```
否则就会抛出一个ReferenceError引用错误
### 类表达式
和函数一样,一个类表达式是定义一个类的另一种方式。类表达式可以是具名的或匿名的。
```js
// 匿名类
let Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
console.log(Person.name); // Person
// 具名类
let Person = class Persona {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
console.log(Person.name); // Persona
```
一个具名类表达式的名称是类内的一个局部属性它可以通过类本身而不是类实例的name属性来获取。
> 类表达式也同样受到类声明中提到的类型提升的限制。
## 类体和方法定义
一个类的类体是一对花括号/大括号{}中的部分。这是定义类成员的位置,如方法或构造函数。
### 严格模式
类声明和类表达式的主体都在严格模式下执行。比如构造函数静态方法原型方法getter和setter都在严格模式下执行。
### 构造函数
`constructor`方法是一个特殊的方法,这个方法用于创建和初始化一个由`class`创建的对象实例。一个类只能拥有一个名为`constructor`的特殊方法。如果类包含多个`constructor`的方法,则将抛出一个`SyntaxError`
一个构造函数可以使用 super 关键字来调用一个父类的构造函数。
### 原型方法
原型方法与使用传统的构造函数方法实现的效果一样。不过class使用的是方法定义从ECMAScript 2015开始在对象初始器中引入了一种更简短定义方法的语法这是一种把方法名直接赋给函数的简写方式。
```js
class Rectangle {
// 构造函数
constructor(h, w) {
this.h = h;
this.w = w;
}
// get
get area() {
return this.calcArea();
}
// 自定义方法
calcArea() {
return this.h * this.w;
}
}
let square = new Rectangle(23, 32);
console.log(square.area) // 736
```
使用类的方式来写这个原型方法使其看上去更加类似于一些常见的面向对象的语言。当然类不会为JS引入新的继承模型所以上述由类写的原型方法也可以使用传统的构造函数来写。不过就是看上去和常见的面向对象的语言不太一样而已。
```js
// 构造函数
function Rectangle(h, w) {
this.h = h;
this.w = w;
}
// 自定义方法
Rectangle.prototype.calcArea = function() {
return this.h * this.w;
}
// get
Object.defineProperty(Rectangle.prototype, 'area', {
get: function() {
return this.calcArea();
}
})
let square = new Rectangle(23, 32);
console.log(square.area); // 736
```
### 静态方法
`static`关键字用于定义一个类的静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例来调用静态方法。
通常,一个函数也是基于对象的,所以函数也有自己的属性,函数的`prototype`就是一个很好的例子。而一个类中的静态方法就相当于给这个构造函数定义了一个它自己的属性。不是在`prototype`上的属性是不会继承到实例上的。
```js
class test {
constructor(xx, yy) {
this.x = xx;
this.y = yy;
}
add() {
return this.x + this.y;
}
static tt() {
return '嘤嘤嘤';
}
}
```
```js
test.tt()
"嘤嘤嘤"
```
静态方法类似于将一个构造函数直接用作于一个工具函数。
### 原型和静态方法包装
`class`体内部执行的代码总是在严格模式下执行,即使没有设置`'use strict'`。所以当调用静态或原型方法时没有指定`this`的值,那么方法内的`this`值将被置为`undefined`
```js
class test {
constructor(x, y) {
this.x = x;
this.y = y;
}
show() {
return this;
}
static ss() {
return this;
}
}
```
这是一个简单的返回`this`的函数,它有两个,分别是继承给实例的属性和静态方法。若`this`的传入值为`undefined`,则在严格模式下不会发生自动装箱,`this`的返回值是`undefined`
```js
let xfy = new test();
xfy.show();
let t = xfy.show;
t(); // undefined
test.ss()
let tt = test.ss;
tt(); // undefined
```
而在传统的构造函数写法下,`this`的值会发生自动装箱,将指向全局对象
```js
function Test(x, y) {
this.x = x;
this.y = y;
}
Test.prototype.show = function () {
return this;
}
Test.ss = function () {
return this;
}
let xfy = new Test();
xfy.show();
let t = xfy.show;
t(); // Global Object
Test.ss()
let tt = Test.ss;
tt(); // Global Object
```
### 实例属性
实例的属性必须定义在类的`constructor`方法里
```js
class test() {
constructor(h, w) {
this.w = w;
this.h = h;
}
}
```
静态的或原型的数据必须定义在类的外面
```js
test.staticWidth = 20;
test.prototype.height = 33;
```

View File

@ -1,5 +1,5 @@
--- ---
title: ASCII在线视频流 title: ASCII 在线视频流
date: 2019-06-29 12:12:41 date: 2019-06-29 12:12:41
tags: Tools tags: Tools
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: Gitlab尝鲜 title: Gitlab 尝鲜
date: 2019-06-19 15:42:41 date: 2019-06-19 15:42:41
tags: Linux tags: Linux
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: Header实践-得拿下这个A title: Header 实践-得拿下这个 A
date: 2019-12-18 16:42:53 date: 2019-12-18 16:42:53
tags: HTML tags: HTML
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: JavaScript-可迭代对象与for-of title: JavaScript-可迭代对象与 for-of
date: 2020-10-29 17:15:48 date: 2020-10-29 17:15:48
tags: JavaScript tags: JavaScript
categories: 笔记 categories: 笔记

View File

@ -1,5 +1,5 @@
--- ---
title: JavaScript实践-乘法表 title: JavaScript 实践-乘法表
date: 2020-12-11 14:58:46 date: 2020-12-11 14:58:46
tags: JavaScript tags: JavaScript
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: JavaScript的作用域与链 title: JavaScript 的作用域与链
date: 2020-07-03 15:13:05 date: 2020-07-03 15:13:05
tags: JavaScript tags: JavaScript
categories: 笔记 categories: 笔记

View File

@ -1,5 +1,5 @@
--- ---
title: JavaScript的函数 title: JavaScript 的函数
index_img: /images/JavaScript的函数/index.webp index_img: /images/JavaScript的函数/index.webp
date: 2020-08-07 11:26:10 date: 2020-08-07 11:26:10
tags: JavaScript tags: JavaScript

View File

@ -1,5 +1,5 @@
--- ---
title: JavaScript笔记-引用类型 title: JavaScript 笔记-引用类型
date: 2020-01-06 09:14:53 date: 2020-01-06 09:14:53
tags: JavaScript tags: JavaScript
categories: 笔记 categories: 笔记

View File

@ -1,5 +1,5 @@
--- ---
title: JavaScript面向对象的程序设计 title: JavaScript 面向对象的程序设计
date: 2020-07-27 16:42:32 date: 2020-07-27 16:42:32
tags: JavaScript tags: JavaScript
categories: 笔记 categories: 笔记

View File

@ -1,5 +1,5 @@
--- ---
title: Node.js之旅 title: Node.js 之旅
index_img: /images/Node.js之旅/2020-09-03-16-16-04.webp index_img: /images/Node.js之旅/2020-09-03-16-16-04.webp
date: 2020-09-03 16:09:29 date: 2020-09-03 16:09:29
tags: [JavaScript, Node] tags: [JavaScript, Node]

View File

@ -1,5 +1,5 @@
--- ---
title: QinQ基础操作 title: QinQ 基础操作
date: 2019-05-29 16:21:15 date: 2019-05-29 16:21:15
tags: Network tags: Network
categories: 网络 categories: 网络

View File

@ -1,5 +1,5 @@
--- ---
title: Resilio Sync多平台实时同步 title: Resilio Sync 多平台实时同步
date: 2019-05-18 12:31:45 date: 2019-05-18 12:31:45
tags: Tools tags: Tools
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: 开黑之路-Teamspeak Server搭建 title: 开黑之路-Teamspeak Server 搭建
date: 2019-05-29 8:05:00 date: 2019-05-29 8:05:00
tags: Player tags: Player
categories: Player categories: Player

View File

@ -1,5 +1,5 @@
--- ---
title: Vue3中的响应数据 title: Vue3 中的响应数据
date: 2020-11-2 10:01:55 date: 2020-11-2 10:01:55
tags: [JavaScript, Vue] tags: [JavaScript, Vue]
categories: 笔记 categories: 笔记
@ -9,13 +9,13 @@ index_img: /images/Vue3中的响应数据/logo.webp
## 实时渲染 ## 实时渲染
在学习Vue2.x的过程中做过一个更改数据从而触发实时渲染DOM的小实例。期间很顺利而后在同样方法测试Vue3的时候发现遇到了一些不同的行为。根据查阅了一些文档以及源码做出了一些推测。 在学习 Vue2.x 的过程中,做过一个更改数据从而触发实时渲染 DOM 的小实例。期间很顺利,而后在同样方法测试 Vue3 的时候发现遇到了一些不同的行为。根据查阅了一些文档以及源码,做出了一些推测。
## 数据与方法 ## 数据与方法
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。 当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
在Vue2.x中可以创建一个数据对象为实例提供数据。虽然这样的写法和直接在实例中为`data`添加属性没有多少差别: Vue2.x 中,可以创建一个数据对象,为实例提供数据。虽然这样的写法和直接在实例中为`data`添加属性没有多少差别:
```html ```html
<div id="app"> <div id="app">
@ -48,15 +48,15 @@ app.message === app.$data.message
app.$data.message === data.message app.$data.message === data.message
``` ```
并且我们单独创建的`data`对象也被转换成了检测数据变化的Observer对象 并且我们单独创建的`data`对象也被转换成了检测数据变化的 Observer 对象
![](../images/Vue3中的响应数据/2020-10-20-14-23-58.webp) ![](../images/Vue3中的响应数据/2020-10-20-14-23-58.webp)
因此,我们在修改`data`对象的内容时app实例的属性也会被改变从而实时渲染到DOM上。 因此,我们在修改`data`对象的内容时app 实例的属性也会被改变,从而实时渲染到 DOM 上。
![](../images/Vue3中的响应数据/2020-10-20-14-25-25.webp) ![](../images/Vue3中的响应数据/2020-10-20-14-25-25.webp)
但在Vue3上发生了一些小小的改变。在Vue3上我们将实例的`data`函数直接return为我们在父作用域中创建的对象这个对象不会被修改为检测属性数据变化的对象。 但在 Vue3 上发生了一些小小的改变。在 Vue3 上,我们将实例的`data`函数直接 return 为我们在父作用域中创建的对象,这个对象不会被修改为检测属性数据变化的对象。
```html ```html
<div id="app"> <div id="app">
@ -77,7 +77,7 @@ app.$data.message === data.message
</script> </script>
``` ```
这里的app是我们创建的实例但最终挂载DOM后返回的实例为vm。不同于2.x的地方是这里我们在父作用域中创建的对象并没用任何的变化它还是一个普通的对象。 这里的app是我们创建的实例但最终挂载 DOM 后返回的实例为 vm。不同于 2.x 的地方是,这里我们在父作用域中创建的对象并没用任何的变化,它还是一个普通的对象。
![](../images/Vue3中的响应数据/2020-10-20-14-31-01.webp) ![](../images/Vue3中的响应数据/2020-10-20-14-31-01.webp)
@ -92,13 +92,13 @@ vm.message === data.message
![](../images/Vue3中的响应数据/2020-10-20-15-42-32.webp) ![](../images/Vue3中的响应数据/2020-10-20-15-42-32.webp)
这一点2和3有着很大的差距,在vue2中我们是可以通过`data`对象来实时更新DOM的。而在3中就不行了。 这一点 2 和 3 有着很大的差距,在 Vue2 中,我们是可以通过`data`对象来实时更新 DOM 的。而在 3 中就不行了。
据我的猜测主要是Vue3没有对父作用域的`data`对象设置Proxy代理的原因。虽然二者已经是互相引用修改一个对象值另一个对象也会被修改。**但是通过修改`data`的属性,并不会触发`vm.$data`对象的`set()`方法。** 据我的猜测,主要是 Vue3 没有对父作用域的`data`对象设置 Proxy 代理的原因。虽然二者已经是互相引用,修改一个对象值,另一个对象也会被修改。**但是通过修改`data`的属性,并不会触发`vm.$data`对象的`set()`方法。**
## 模仿行为 ## 模仿行为
我使用了一个小例子模仿了一下Vue3的行为 我使用了一个小例子,模仿了一下 Vue3 的行为:
```js ```js
// 这是在父作用域中的data对象它是一个普通对象 // 这是在父作用域中的data对象它是一个普通对象
@ -118,16 +118,16 @@ let handler = {
let vm = new Proxy(data, handler); let vm = new Proxy(data, handler);
``` ```
这是一个很简单的例子,我们为`vm`对象设置了一个来自`data`对象的代理。现在二者就是互相引用的关系了就和Vue3一样。 这是一个很简单的例子,我们为`vm`对象设置了一个来自`data`对象的代理。现在二者就是互相引用的关系了,就和 Vue3 一样。
```js ```js
data.message === vm.message data.message === vm.message
// true // true
``` ```
我在代理的拦截中配置了一个setter`vm`对象成功设置了值后就会触发这个setter并在控制台打印一则信息。用来模拟更新DOM。也就是说现在的`vm`实例就相当于Vue实例当我更新其属性时会在控制台动态的打印信息就相当于实时更新了DOM。就和Vue实例一样。 我在代理的拦截中配置了一个 setter`vm`对象成功设置了值后,就会触发这个 setter ,并在控制台打印一则信息。用来模拟更新 DOM。也就是说现在的`vm`实例就相当于 Vue 实例,当我更新其属性时,会在控制台动态的打印信息,就相当于实时更新了 DOM。就和 Vue 实例一样。
现在我们直接对`vm.message`赋值则会成功触发预先设置的setter函数成功的更新了值并且在控制打印了消息。 现在我们直接对`vm.message`赋值,则会成功触发预先设置的 setter 函数,成功的更新了值并且在控制打印了消息。
```js ```js
vm.message vm.message
@ -144,7 +144,7 @@ data.message
// "hello xfy" // "hello xfy"
``` ```
直接设置`data.message`可以成功修改`vm.message`的值,但是却不会触发`vm`对象的setter方法。 直接设置`data.message`可以成功修改`vm.message`的值,但是却不会触发`vm`对象的 setter 方法。
```js ```js
data.message = '嘤嘤嘤'; data.message = '嘤嘤嘤';
@ -154,10 +154,10 @@ vm.message
// "嘤嘤嘤" // "嘤嘤嘤"
``` ```
这里的小例子最简化的模拟了Vue3的实例行为在真正的Vue3的实例上我们也可以很清晰的看到其Proxy属性 这里的小例子最简化的模拟了 Vue3 的实例行为,在真正的 Vue3 的实例上,我们也可以很清晰的看到其 Proxy 属性:
![](../images/Vue3中的响应数据/2020-10-20-16-28-03.webp) ![](../images/Vue3中的响应数据/2020-10-20-16-28-03.webp)
## 总结 ## 总结
总的来说就是因为data对象修改时不会触发实例的set方法但数据依然会改变只是DOM不会实时更新。 总的来说就是因为 data 对象修改时不会触发实例的 set 方法,但数据依然会改变,只是 DOM 不会实时更新。

View File

@ -1,5 +1,5 @@
--- ---
title: systemd的基础操作 title: systemd 的基础操作
date: 2019-06-14 14:42:41 date: 2019-06-14 14:42:41
tags: Linux tags: Linux
categories: Linux categories: Linux

View File

@ -1,5 +1,5 @@
--- ---
title: 使typecho支持emoji🎈 title: 使 typecho 支持 emoji🎈
date: 2019-05-12 13:41:57 date: 2019-05-12 13:41:57
tags: typecho tags: typecho
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: 修改WindowsiCloud云盘存储位置 title: 修改 WindowsiCloud 云盘存储位置
date: 2019-11-18 12:53:53 date: 2019-11-18 12:53:53
tags: Windows tags: Windows
categories: 踩坑 categories: 踩坑

View File

@ -1,5 +1,5 @@
--- ---
title: 入坑IRC title: 入坑 IRC
index_img: /images/入坑IRC/logo.webp index_img: /images/入坑IRC/logo.webp
date: 2020-08-22 18:37:36 date: 2020-08-22 18:37:36
tags: [tools,IRC,Linux] tags: [tools,IRC,Linux]

View File

@ -7,17 +7,17 @@ url: public-key-cryptgraphy
index_img: /images/公开密钥密码学/logo.webp index_img: /images/公开密钥密码学/logo.webp
--- ---
GPG/PGP赛高 GPG/PGP 赛高!
## 什么是非对称加密? ## 什么是非对称加密?
人类的历史上加密走了很长的一段路程。想尽了各种办法来保护自己那不想让不该知道的人知道的东西。 加密这东西,在密码学中最直白的解释就是将一般的明文信息改变为难以读取的内容,使其不可读的过程只有拥有解密方法的对象,经由解密过程,才能将密文还原为正常可读的内容。 人类的历史上加密走了很长的一段路程。想尽了各种办法来保护自己那不想让不该知道的人知道的东西。 加密这东西,在密码学中最直白的解释就是将一般的明文信息改变为难以读取的内容,使其不可读的过程只有拥有解密方法的对象,经由解密过程,才能将密文还原为正常可读的内容。
大概在1970年代中期所谓的“强加密”的使用开始从政府保密机构延申至公共的领域了也就是说开始到我们大众都开始接触了。当今世界加密已经是我们的日常生活中常常见到的东西了。 大概在 1970 年代中期,所谓的“强加密”的使用开始从政府保密机构延申至公共的领域了,也就是说开始到我们大众都开始接触了。当今世界,加密已经是我们的日常生活中常常见到的东西了。
例如我们常常访问的带有SSL/TLS的网站这也是非对称加密的一种。 所谓的对称加密,它也是密码学中的一种。但他与对称加密不同的是,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。 例如我们常常访问的带有 SSL/TLS 的网站,这也是非对称加密的一种。所谓的对称加密,它也是密码学中的一种。但他与对称加密不同的是,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。
由于加密和解密需要两个不同的密钥,故被称为非对称加密; 不同于加密和解密都使用同一个密钥的对称加密。虽然两个密钥在数学上相关,但如果知道了中一个,并不能凭此计算出另外一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥为私钥,必须由用户自行严格秘密保管,绝不透过任途径向任何人提供,也不会透露给被信任的要通信的另一方。 由于加密和解密需要两个不同的密钥,故被称为非对称加密; 不同于加密和解密都使用同一个密钥的对称加密。虽然两个密钥在数学上相关,但如果知道了中一个,并不能凭此计算出另外一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥为私钥,必须由用户自行严格秘密保管,绝不透过任途径向任何人提供,也不会透露给被信任的要通信的另一方。
### 加密 ### 加密
@ -27,22 +27,22 @@ GPG/PGP赛高
在现实世界上可作比拟的例子是,一个传统保管箱,开门和关门都是使用同一条钥匙,这是对称加密;而一个公开的邮箱,投递口是任何人都可以寄信进去的,这可视为公钥;而只有信箱主人拥有钥匙可以打开信箱,这就视为私钥。 在现实世界上可作比拟的例子是,一个传统保管箱,开门和关门都是使用同一条钥匙,这是对称加密;而一个公开的邮箱,投递口是任何人都可以寄信进去的,这可视为公钥;而只有信箱主人拥有钥匙可以打开信箱,这就视为私钥。
常见的公钥加密算法有RSA、ElGamal、背包算法、RabinRSA的特例、迪菲赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法英语Elliptic Curve Cryptography, ECC。使用最广泛的是RSA算法由发明者Rivest、Shmir和Adleman姓氏首字母缩写而来是著名的公开秘钥加密算法ElGamal是另一种常用的非对称加密算法。 常见的公钥加密算法有RSA、ElGamal、背包算法、RabinRSA的特例、迪菲赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法英语Elliptic Curve Cryptography, ECC。使用最广泛的是 RSA 算法由发明者Rivest、Shmir和Adleman姓氏首字母缩写而来是著名的公开秘钥加密算法ElGamal 是另一种常用的非对称加密算法。
#### 加密过程 #### 加密过程
直白的解释: Tom 和 Jerry想发送一些消息/文件而不被隔壁的Spike知道文件的内容。于是它们机智的采用了非对称加密来保证内容的安全性。 直白的解释Tom 和 Jerry 想发送一些消息/文件,而不被隔壁的 Spike 知道文件的内容。于是它们机智的采用了非对称加密来保证内容的安全性。
1. Tom先生非对称的两个密钥分别为公钥A私钥B 1. Tom 先生非对称的两个密钥,分别为公钥 A私钥 B
2. 为了能让Jerry发过来的消息被加密了Tom先将可公开的公钥A发给Jerry 2. 为了能让 Jerry 发过来的消息被加密了Tom 先将可公开的公钥 A 发给 Jerry
3. 因为公钥A是完全可公开的所以Spike知道也没关系 3. 因为公钥 A 是完全可公开的,所以 Spike 知道也没关系
4. Jerry收到Tom发的公钥A并将自己的文件X使用公钥A进行加密 4. Jerry 收到 Tom 发的公钥 A并将自己的文件 X 使用公钥 A 进行加密
5. 随后Jerry就可以将加密的文件A(X)正大光明的发送给Tom了 5. 随后 Jerry 就可以将加密的文件 A(X) 正大光明的发送给 Tom
6. 此时的Spike就算截取到加密过的文件A(X)也没有用 6. 此时的 Spike 就算截取到加密过的文件 A(X) 也没有用
7. 因为Tom收到的加密文件A(X)只有它自己的私钥B能够解密,于是它收到后可以使用私钥B正常解密 7. 因为 Tom 收到的加密文件 A(X) 只有它自己的私钥 B 能够解密,于是它收到后可以使用私钥 B 正常解密
8. 所以如果Tom丢失了它的私钥B那么Tom and Jerry都无法读取加密的文件A(X)了 8. 所以如果 Tom 丢失了它的私钥 B那么 Tom and Jerry 都无法读取加密的文件 A(X)
9. (没有私钥就无法解开公钥加密过的信息) 9. (没有私钥就无法解开公钥加密过的信息)
10. 相反Jerry也可以将自己的公钥发给Tom使其加密要发给自己的信息。 10. 相反Jerry 也可以将自己的公钥发给 Tom使其加密要发给自己的信息。
#### 数字签名 #### 数字签名
@ -52,9 +52,9 @@ GPG/PGP赛高
## 非对称加密的软件 ## 非对称加密的软件
对于软件来说我们可能经常听说到GPG这一词。GPG的全称是GNU Privacy GuardGnuPGGPG。它是一款非对称加密的软件是PGP加密软件的满足GPL的替代物。 也就是说它相对于PGP加密来说它是一款开源软件。 对于软件来说,我们可能经常听说到 GPG 这一词。GPG 的全称是 GNU Privacy GuardGnuPGGPG。它是一款非对称加密的软件 PGP 加密软件的满足 GPL 的替代物。 也就是说它相对于 PGP 加密来说,它是一款开源软件。
因为PGP的非对称的算法是开源的所以GPGPGP原理是完全一样的。通常我们会见到GPG/PGP。 所以PGP就可以简单了解到它是一款非开源的非对称加密软件了。 PGP英语Pretty Good Privacy中文翻译“优良保密协议”是一套用于讯息加密、验证的应用程序采用IDEA的散列算法作为加密和验证之用。 因为 PGP 的非对称的算法是开源的,所以 GPGPGP 原理是完全一样的。通常我们会见到 GPG/PGP。 所以 PGP 就可以简单了解到它是一款非开源的非对称加密软件了。 PGP英语Pretty Good Privacy中文翻译“优良保密协议”是一套用于讯息加密、验证的应用程序采用 IDEA 的散列算法作为加密和验证之用。
### 多平台的安装与使用 ### 多平台的安装与使用
@ -64,11 +64,11 @@ GPG/PGP赛高
#### Windows GPG4win #### Windows GPG4win
安装就不再多说GPG4win的官网有打包好的exe可执行程序我们直接下载双击安装就好安装过程也非常的简单不需要进行任何配置。也就是常说的“无脑next☀”。 安装就不再多说GPG4win 的官网有打包好的 exe 可执行程序,我们直接下载双击安装就好,安装过程也非常的简单,不需要进行任何配置。也就是常说的“无脑 next☀”。
[Download](<https://www.gpg4win.org/get-gpg4win.html>) [Download](<https://www.gpg4win.org/get-gpg4win.html>)
GPG4win是GPG在Windows平台的一款可视化的非对称加密软件。对于可视化的软件来说使用也非常的简单明了。 几乎常用的一些功能都非常直白的写在了开打的页面中。基本上只要使用者了解大概的非对称加密的运作原理,就可以很轻松的使用该软件了。 GPG4win 是 GPG 在 Windows 平台的一款可视化的非对称加密软件。对于可视化的软件来说,使用也非常的简单明了。 几乎常用的一些功能都非常直白的写在了开打的页面中。基本上只要使用者了解大概的非对称加密的运作原理,就可以很轻松的使用该软件了。
![Kleopatra](../images/公开密钥密码学/Kleopatra.webp) ![Kleopatra](../images/公开密钥密码学/Kleopatra.webp)
@ -76,7 +76,7 @@ GPG/PGP赛高
#### Ubuntu & CentOS #### Ubuntu & CentOS
目前最新的UbuntuCentOS的发行版中都带有GnuPrivacyGuard。也就是GPG的一种所以使用的方法也是大同小异了。 以Ubuntu为例 * 创建密钥 目前最新的 UbuntuCentOS 的发行版中都带有 GnuPrivacyGuard。也就是 GPG 的一种,所以使用的方法也是大同小异了。 以 Ubuntu 为例: * 创建密钥
``` ```
gpg --gen-key gpg --gen-key
@ -133,7 +133,7 @@ trust
## 我的公钥 ## 我的公钥
如果有小伙伴想和我扮演Tom and Jerry的话或者想校验我的签名的文件的话。欢迎使用下述公钥 如果有小伙伴想和我扮演 Tom and Jerry 的话,或者想校验我的签名的文件的话。欢迎使用下述公钥
[我的公钥🔒!](https://1drv.ms/u/s!ArC4gW7Dc7wWhd5PD8R_o6Mmhp2LxA?e=Ivpa8X) [我的公钥🔒!](https://1drv.ms/u/s!ArC4gW7Dc7wWhd5PD8R_o6Mmhp2LxA?e=Ivpa8X)

View File

@ -1,5 +1,5 @@
--- ---
title: 想起来当年还折腾过hexo title: 想起来当年还折腾过 hexo
date: 2018-06-29 12:12:41 date: 2018-06-29 12:12:41
tags: [Linux, HTML] tags: [Linux, HTML]
categories: 实践 categories: 实践

View File

@ -1,5 +1,5 @@
--- ---
title: 某咸鱼的AJAX入门🐟 title: 某咸鱼的 AJAX 入门🐟
date: 2020-12-24 19:41:02 date: 2020-12-24 19:41:02
tags: JavaScript tags: JavaScript
categories: 笔记 categories: 笔记
@ -9,9 +9,9 @@ index_img: /images/某咸鱼的AJAX入门/logo.webp
## Ajax ## Ajax
AJAX是异步的JavaScriptXMLAsynchronous JavaScript And XML。简单点说就是使用`XMLHttpRequest`对象与服务器通信。 它可以使用JSONXMLHTMLtext文本等格式发送和接收数据。AJAX最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。 Ajax 是异步的 JavaScriptXMLAsynchronous JavaScript And XML。简单点说就是使用`XMLHttpRequest`对象与服务器通信。 它可以使用 JSONXMLHTMLtext 文本等格式发送和接收数据。Ajax 最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。
AJAX最主要的两个特性: Ajax 最主要的两个特性:
* 在不重新加载页面的情况下发送请求给服务器。 * 在不重新加载页面的情况下发送请求给服务器。
* 接受并使用从服务器发来的数据。 * 接受并使用从服务器发来的数据。
@ -26,7 +26,7 @@ AJAX最主要的两个特性
let httpRequest = new XMLHttpRequest(); let httpRequest = new XMLHttpRequest();
``` ```
大部分现代浏览器都实现了`XMLHttpRequest`方法当然也包括微软。不过早期的IE6或之前的浏览器是通过`ActiveXObject`方法来实现的。为了兼容早期的IE浏览器我们可能需要这要写 大部分现代浏览器都实现了`XMLHttpRequest`方法当然也包括微软。不过早期的IE6或之前的浏览器是通过`ActiveXObject`方法来实现的。为了兼容早期的 IE 浏览器,我们可能需要这要写:
```js ```js
if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ... if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ...
@ -36,7 +36,7 @@ if (window.XMLHttpRequest) { // Mozilla, Safari, IE7+ ...
} }
``` ```
不过随着时间的流逝IE6早已被淘汰所以目前的兼容性无需多虑。 不过随着时间的流逝IE6 早已被淘汰,所以目前的兼容性无需多虑。
当发送了一个请求之后就是得到相应的响应。得到响应后我们需要通知JS如何处理这时就需要给实例的`onreadystatechange`属性赋值一个方法,当请求状态改变时调用该方法。 当发送了一个请求之后就是得到相应的响应。得到响应后我们需要通知JS如何处理这时就需要给实例的`onreadystatechange`属性赋值一个方法,当请求状态改变时调用该方法。
@ -52,22 +52,22 @@ httpRequest.onreadystatechange = () => {
} }
``` ```
当我们能够处理响应的时候就可以发送一个实际的请求了。通过调用HTTP请求对象的`open()``send()`方法: 当我们能够处理响应的时候,就可以发送一个实际的请求了。通过调用 HTTP 请求对象的`open()``send()`方法:
```js ```js
httpRequest.open('GET', 'https://www.defectink.com/balabala.html', true); httpRequest.open('GET', 'https://www.defectink.com/balabala.html', true);
httpRequest.send(); httpRequest.send();
``` ```
* `open()`的第一个参数是HTTP请求方法 - 有GETPOSTHEAD以及服务器支持的其他方法。 保证这些方法一定要是大写字母否则其他一些浏览器比如FireFox可能无法处理这个请求。 * `open()`的第一个参数是 HTTP 请求方法 - 有 GETPOSTHEAD 以及服务器支持的其他方法。 保证这些方法一定要是大写字母,否则其他一些浏览器(比如 FireFox可能无法处理这个请求。
* 第二个参数是要发送的URL。由于安全原因默认不能调用第三方URL域名。 * 第二个参数是要发送的 URL。由于安全原因默认不能调用第三方 URL 域名。
* 第三个可选参数是用于设置请求是否是异步的。true为默认值。 * 第三个可选参数是用于设置请求是否是异步的。true 为默认值。
## 处理服务器响应 ## 处理服务器响应
`onreadystatechange`被赋值的函数负责处理响应,这个函数首先要检查请求的状态,根据状态来决定后面执行的任务。 `onreadystatechange`被赋值的函数负责处理响应,这个函数首先要检查请求的状态,根据状态来决定后面执行的任务。
如果状态的值是`XMLHttpRequest.DONE`对应的值是4意味着服务器响应收到了并且是没问题的然后就可以继续执行。 如果状态的值是`XMLHttpRequest.DONE`(对应的值是 4意味着服务器响应收到了并且是没问题的然后就可以继续执行。
```js ```js
if (httpRequest.readyState === 4) { if (httpRequest.readyState === 4) {
@ -85,7 +85,7 @@ if (httpRequest.readyState === 4) {
* 3 (交互) or (正在处理请求) * 3 (交互) or (正在处理请求)
* 4 (完成) or (请求已完成并且响应已准备好) * 4 (完成) or (请求已完成并且响应已准备好)
当然接下来再继续检查HTTP的`response code`。可以通过响应码200来判断AJAX有没有成功。 当然接下来再继续检查 HTTP `response code`。可以通过响应码 200 来判断Ajax有没有成功。
```js ```js
if (httpRequest.status === 200) { if (httpRequest.status === 200) {
@ -97,10 +97,10 @@ if (httpRequest.status === 200) {
} }
``` ```
当检查完请求状态和HTTP响应码后 就可以使用服务器返回的数据了。有两种方法来访问这些数据: 当检查完请求状态和 HTTP 响应码后, 就可以使用服务器返回的数据了。有两种方法来访问这些数据:
* `httpRequest.responseText` 服务器以文本字符的形式返回 * `httpRequest.responseText` 服务器以文本字符的形式返回
* `httpRequest.responseXML` 以 XMLDocument 对象方式返回之后就可以使用JavaScript来处理 * `httpRequest.responseXML` 以 XMLDocument 对象方式返回,之后就可以使用 JavaScript 来处理
当然这一步只有在发起的请求是异步的时候有效。如果发起的请求时同步请求则不必使用函数,但是并不推荐这样做。 当然这一步只有在发起的请求是异步的时候有效。如果发起的请求时同步请求则不必使用函数,但是并不推荐这样做。
@ -144,15 +144,15 @@ function handler() {
} }
``` ```
## jQuery中的AJAX ## jQuery中的Ajax
jquery极大的简化了原生js的一些繁琐操作,同时它也提供一些ajax方法来简化操作。 jQuery 极大的简化了原生 JavaScript 的一些繁琐操作,同时它也提供一些 Ajax 方法来简化操作。
### ajax方法 ### ajax方法
jQuery提供了一个`$.ajax()`方法,方便去操作ajax。该方法是 jQuery 底层 AJAX 实现。简单易用的高层实现见`$.get`,`$.post`等。`$.ajax()`返回其创建的 XMLHttpRequest 对象。大多数情况下无需直接操作该函数。 jQuery 提供了一个`$.ajax()`方法,方便去操作 Ajax。该方法是 jQuery 底层 Ajax 实现。简单易用的高层实现见`$.get`,`$.post`等。`$.ajax()`返回其创建的 XMLHttpRequest 对象。大多数情况下无需直接操作该函数。
这个方法接受一个参数,这个参数为键值对集合(对象),其中包含了AJAX 请求的键值对集合,所有选项都是可选的。也可以通过`$.ajaxSetup()`设置任何选项的默认值。 这个方法接受一个参数,这个参数为键值对集合(对象),其中包含了 Ajax 请求的键值对集合,所有选项都是可选的。也可以通过`$.ajaxSetup()`设置任何选项的默认值。
```js ```js
$.ajax({ $.ajax({
@ -163,7 +163,7 @@ $.ajax({
### 回调 ### 回调
和原生js一样jQuery也可以通过参数来设定是否同步执行async。当异步执行时可以使用`success`参数来执行一个回调函数。回调函数支持传递一个参数参数为response。 和原生 JavaScript 一样jQuery 也可以通过参数来设定是否同步执行async。当异步执行时可以使用`success`参数来执行一个回调函数。回调函数支持传递一个参数,参数为 response。
```js ```js
$.ajax({ $.ajax({
@ -176,9 +176,9 @@ $.ajax({
### 其他方法 ### 其他方法
jQuery同样提供了一些其他简易易用的方法例如`load()`方法,通过 AJAX 请求从服务器加载数据,并把返回的数据放置到指定的元素中。 jQuery 同样提供了一些其他简易易用的方法,例如`load()`方法,通过 Ajax 请求从服务器加载数据,并把返回的数据放置到指定的元素中。
按照传统的方法利用jQuery来写一个传递文本到元素可能需要这样 按照传统的方法利用 jQuery 来写一个传递文本到元素可能需要这样:
```js ```js
$(document).ready(function () { $(document).ready(function () {
@ -205,4 +205,4 @@ $(document).ready(function () {
不过越是简洁的方法越是难以捉摸以及不方便自定义其他的参数。 不过越是简洁的方法越是难以捉摸以及不方便自定义其他的参数。
无论怎么说jQuery提供了更加便利的手段来完成原本繁琐的事情且仅仅只是多用了300+kb的源码。 无论怎么说jQuery 提供了更加便利的手段来完成原本繁琐的事情,且仅仅只是多用了 300+kb 的源码。

View File

@ -0,0 +1,443 @@
---
title: 真的是在写 JS - JavaScript 的类
date: 2020-12-30 11:14:16
tags: JavaScript
categories: 笔记
url: really-writing-js-javascriptclasses
index_img: /images/真的是在写JS-JavaScript的类/2020-12-30-11-06-25.webp
---
在 JavaScript 中所谓的类不过是 ECMAScript 2015 为其引入的语法糖。这个糖它只有甜味,它是构造函数的另一种写法,类语法**不会**为 JavaScript 引入新的面向对象的继承模型。
在之前学习 [JS面向对象](https://www.defectink.com/defect/javascript-object-oriented-programming.html) 的编程时,详细的研究过了关于 JavaScript 构造函数以及继承的问题。从工厂模式一直发展至今的寄生式继承,也解决了很多语言本有的问题。虽然类只是个语法糖,但是从很多地方来说它也解决了关于构造函数继承等的复杂写法。
## 📍定义类
类实际上是一个特殊的函数,将像函数能够定义的函数表达式和函数声明一样,类语法有两个部分组成:类表达式和类声明。
### 声明
定义一个类的方法是使用一个类声明,要声明一个类,可以使用带有`class`关键字的类名。一个类看上去像这样:
```js
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
otherFunc() {
console.log(name + age);
}
}
```
这和一些常见的语言,例如 C艹 和 Java 较为类似。相比较之下,传统的 JavaScript 构造函数对于其他的面向对象语言的程序员可能不太容易理解,来回顾下传统的构造函数。
```js
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.otherFunc() {
console.log(name + age);
}
```
相较于最传统的构造函数来说,类的声明方式会更让人容易理解。虽然声明看上去只是换了种写法(确实就是换了种写法,包括继承),但是对于继承等操作来说,`class`类的方式省了不少事。
### 提升
函数声明和类声明之间的一个重要区别是函数声明会提升,而类声明不会。和新关键字`let``const`等一样,类需要先声明再使用。
```js
let xfy = new Person(); // ReferenceError
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
```
否则就会抛出一个 ReferenceError引用错误
### 类表达式
和函数一样,一个类表达式是定义一个类的另一种方式。类表达式可以是具名的或匿名的。
```js
// 匿名类
let Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
console.log(Person.name); // Person
// 具名类
let Person = class Persona {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
console.log(Person.name); // Persona
```
一个具名类表达式的名称是类内的一个局部属性,它可以通过类本身(而不是类实例)的 name 属性来获取。
> 类表达式也同样受到类声明中提到的类型提升的限制。
## 类体和方法定义
一个类的类体是一对花括号/大括号`{}`中的部分。这是定义类成员的位置,如方法或构造函数。
### 严格模式
类声明和类表达式的主体都在严格模式下执行。比如构造函数静态方法原型方法getter 和 setter 都在严格模式下执行。
### 构造函数
`constructor`方法是一个特殊的方法,这个方法用于创建和初始化一个由`class`创建的对象实例。一个类只能拥有一个名为`constructor`的特殊方法。如果类包含多个`constructor`的方法,则将抛出一个`SyntaxError`
一个构造函数可以使用`super`关键字来调用一个父类的构造函数。
### 原型方法
原型方法与使用传统的构造函数方法实现的效果一样。不过 class 使用的是方法定义:从 ECMAScript 2015 开始,在对象初始器中引入了一种更简短定义方法的语法,这是一种把方法名直接赋给函数的简写方式。
```js
class Rectangle {
// 构造函数
constructor(h, w) {
this.h = h;
this.w = w;
}
// get
get area() {
return this.calcArea();
}
// 自定义方法
calcArea() {
return this.h * this.w;
}
}
let square = new Rectangle(23, 32);
console.log(square.area) // 736
```
使用类的方式来写这个原型方法使其看上去更加类似于一些常见的面向对象的语言。当然,类不会为 JavaScript 引入新的继承模型,所以上述由类写的原型方法也可以使用传统的构造函数来写。不过就是看上去和常见的面向对象的语言不太一样而已。
```js
// 构造函数
function Rectangle(h, w) {
this.h = h;
this.w = w;
}
// 自定义方法
Rectangle.prototype.calcArea = function() {
return this.h * this.w;
}
// get
Object.defineProperty(Rectangle.prototype, 'area', {
get: function() {
return this.calcArea();
}
})
let square = new Rectangle(23, 32);
console.log(square.area); // 736
```
### 静态方法
`static`关键字用于定义一个类的静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例来调用静态方法。
通常,一个函数也是基于对象的,所以函数也有自己的属性,函数的`prototype`就是一个很好的例子。而一个类中的静态方法就相当于给这个构造函数定义了一个它自己的属性。不是在`prototype`上的属性是不会继承到实例上的。
```js
class test {
constructor(xx, yy) {
this.x = xx;
this.y = yy;
}
add() {
return this.x + this.y;
}
static tt() {
return '嘤嘤嘤';
}
}
```
```js
test.tt()
"嘤嘤嘤"
```
静态方法类似于将一个构造函数直接用作于一个工具函数。
### 原型和静态方法包装
`class`体内部执行的代码总是在严格模式下执行,即使没有设置`'use strict'`。所以当调用静态或原型方法时没有指定`this`的值,那么方法内的`this`值将被置为`undefined`
```js
class test {
constructor(x, y) {
this.x = x;
this.y = y;
}
show() {
return this;
}
static ss() {
return this;
}
}
```
这是一个简单的返回`this`的函数,它有两个,分别是继承给实例的属性和静态方法。若`this`的传入值为`undefined`,则在严格模式下不会发生自动装箱,`this`的返回值是`undefined`
```js
let xfy = new test();
xfy.show();
let t = xfy.show;
t(); // undefined
test.ss()
let tt = test.ss;
tt(); // undefined
```
而在传统的构造函数写法下,`this`的值会发生自动装箱,将指向全局对象
```js
function Test(x, y) {
this.x = x;
this.y = y;
}
Test.prototype.show = function () {
return this;
}
Test.ss = function () {
return this;
}
let xfy = new Test();
xfy.show();
let t = xfy.show;
t(); // Global Object
Test.ss()
let tt = Test.ss;
tt(); // Global Object
```
### 实例属性
实例的属性必须定义在类的`constructor`方法里
```js
class test() {
constructor(h, w) {
this.w = w;
this.h = h;
}
}
```
## 字段声明
> 公共和私有字段声明是JavaScript标准委员会 [TC39](https://tc39.es/) 提出的 [实验性功能第3阶段](https://github.com/tc39/proposal-class-fields) 。浏览器中的支持是有限的,但是可以通过 [Babel](https://babeljs.io/) 等系统构建后使用此功能。
### 公有字段声明
class 提供了声明公有字段的方法,通过预先声明字段,类定义变得更加自我记录,并且字段始终存在。
```js
class Rectangle {
x;
constructor(height, width) {
this.height = height;
this.width = width;
}
static y = 1;
clicked() {
this.x++;
}
}
let square = new Rectangle(123, 33);
```
正如上面看到的,这个字段可以用也可以不用默认值来声明。
共有字段重要的不同之处在于,它们会在每个独立对象中被设好,而不是设在`prototype`中。
```js
Rectangle.prototype.x // undefined
```
### 私有字段声明
从类外部引用私有字段是错误的。它们只能在类里面中读取或写入。通过定义在类外部不可见的内容,可以确保类的用户不会依赖于内部,因为内部可能在不同版本之间发生变化。
私有字段从大佬们之间共有约定的下划线,现在已经换为草案中的`#`了。
```js
class Rectangle {
#x;
#width;
#height;
constructor(w, h) {
this.#width = w;
this.#height = h;
}
get area() {
return this.#width * this.#height;
}
}
let s = new Rectangle(12, 33);
```
> 私有字段必须先在类中声明。
## 使用`extends`扩展子类
`extends`关键字在*类声明*或*类表达式*中用于创建一个类作为另一个类的一个子类。也就是说继承。ECMAScript 2015 的继承终于变的更加直观了。
```js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayName() {
return this.name;
}
get newYear() {
return ++this.age;
}
}
class Student extends Person {
constructor(name, age, classes) {
super(name, age);
this.classes = classes;
}
}
```
不需要再手动写一个用于继承的函数了,也不需要再使用`call`了,在子类中可以直接使用`super()`方法来指向超类的`this`
当然,因为是语法糖,所以`extends`也可以继承传统基于函数的“类”:
```js
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
return this.name;
}
Object.defineProperty(Person.prototype, 'newYear', {
get: function () {
return ++this.age;
}
})
class Student extends Person {
constructor(name, age, classess) {
super(name, age);
this.classess = classess;
}
sayAge() {
return this.age;
}
}
```
除了基于函数的“类”,其他用法还是一模一样。
**但是**,类无法继承常规对象,也就是非构造函数的`prototype`,或者说不可构造的常规对象。如果需要继承,可以使用`Object.setPrototypeOf()`方法。
```js
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let someOne= {
sayName() {
return this.name;
}
}
Object.setPrototypeOf(Student.prototype, someOne);
let p = new Student('xfy', 18);
```
又见到老朋友`prototype`了。
### Species
如果我们想在一个派生出数组的构造函数中返回 Array 对象,那么 Species 允许我们覆盖默认的构造函数
```js
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
let a = new MyArray(1,2,3,4);
```
## 不仅仅是语法糖
前面有提到过class 不过是新的语法糖。所谓的语法糖,就是旨在不增加语法的情况下,使得代码内容更加容易阅读。根据前面的一些实例,可以发现 class 完全可以使用以前的纯函数来写出相同的内容。
不过他们也有一些重大差异:
1. `class` 创建的函数具有特殊的内部属性标记 `[[FunctionKind]]:"classConstructor"`。所以它与手动创建构造函数并不完全相同,并且 JavaScript 会在很多地方检查该属性。例如类无法当作普通函数来使用,必须配合`new`操作符来使用:
```js
Error: Class constructor User cannot be invoked without 'new'
```
2. 类的方法不可枚举,类定义将 `"prototype"` 中的所有方法的 `enumerable` 标志设置为 `false`。所以当使用`for/in`方法时,类的方法是不会被枚举出来的。
3. 类总是使用`use strict`,类中的所有代码都是严格模式。所以也不会导致 this 的自动装箱。
4. 函数的声明会提升,类的声明不会。
5. `class`内创建的方法既能继承作用域链,同时也是在 prototype 上,不会为每个实例创建同名不相同的方法。
## 参考
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes
* https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Class_elements
* https://zh.javascript.info/class
头图来自:[Classes in JavaScript](https://dev.to/samanthaming/classes-in-javascript-30n8)

View File

@ -1,3 +1,11 @@
<!--
* @Author: your name
* @Date: 2020-11-06 11:52:50
* @LastEditTime: 2020-12-30 11:16:47
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \blog\source\_posts\自动备份大法.md
-->
--- ---
title: 自动备份大法 title: 自动备份大法
date: 2019-06-05 16:41:57 date: 2019-06-05 16:41:57

View File

@ -1,5 +1,5 @@
--- ---
title: 解决inotify watch不够⌚ title: 解决 inotify watch 不够⌚
date: 2019-05-29 9:05:01 date: 2019-05-29 9:05:01
tags: Linux tags: Linux
categories: 踩坑 categories: 踩坑

View File

@ -10,7 +10,7 @@ layout: about
才会成长。 才会成长。
Wordpresstypecho换来换去的主题最后还是选择了纯静态。或许这次才会真的沉静下浮躁的心来。 Wordpresstypecho换来换去的主题最后还是选择了纯静态。或许这次才会真的沉静下浮躁的心来。
## 到底还是 ## 到底还是
@ -26,4 +26,8 @@ Wordpress到typecho换来换去的主题最后还是选择了纯静态。
## 还活着的服务 ## 还活着的服务
<a class="btn" href="https://stats.defectink.com/" title="status" target="_blank">监控👀</a> <a class="btn" href="https://stats.defectink.com/" title="status" target="_blank">监控👀</a>
## 排版
某咸鱼正在根据 [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) 来更新文章。

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB