0%

How to get DOM elements on page

获取Dom元素总体来说有两类方法:

  • getElementByXXX
  • queryXXX

这两种方法都是从document对象开始查找,所以可以直接使用,不需要先获取document对象。

get方法

getElementById

这是最常用的方法,如果你的元素有id属性,那么可以使用这个方法获取元素,返回的是一个Element对象。如果没有找到,则返回null。
html代码:

1
<div id="root"></div>

js代码:

1
2
3
4
const element = document.getElementById('root');
if (element) {
// do something
}

getElementsByClassName

这个方法可以获取到所有class属性中包含指定类名的元素,返回的是一个array-like对象HTMLCollection,可以使用Array.from()方法将其转换为数组。
html代码:

1
2
3
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>

js代码:

1
2
const elements = document.getElementsByClassName('container');
const elementsArray = Array.from(elements);

需要注意的是,这个方法返回的是一个动态的集合,也就是说,如果你在获取到元素之后,再向页面中添加或者删除元素,那么这个集合也会跟着改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<button class="button">A</button>
<button class="button">B</button>
<button class="button">C</button>
<script>
// 初始时,页面上有三个按钮。
const buttons = document.getElementsByClassName("button");
console.log(buttons); // HTMLCollection(3) [button, button, button]

// 添加一个按钮, 此时再打印buttons,则输出四个按钮。
const buttonD = document.createElement("button");
buttonD.innerHTML = "D";
buttonD.className = "button";
document.body.appendChild(buttonD);
console.log(buttons); // HTMLCollection(4) [button, button, button, button]
</script>
</body>

query方法

query类的方法参数是CSS选择器,比如

  • “#root” - 查找id为root的结点,
  • “.container” - 查找class为container的结点,
  • “div” - 查找所有div结点。

querySelector

这个方法可以获取到第一个匹配的元素,它的返回值类型是Element。如果没有匹配的元素,返回null。

1
<div class="container"></div>

js代码:

1
const element = document.querySelector('.container');

querySelectorAll

这个方法可以获取到所有匹配的元素,返回的是一个类数组对象NodeList,可以使用Array.from()方法将其转换为数组。

1
2
3
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>

js代码:

1
2
const elements = document.querySelectorAll('.container');
const elementsArray = Array.from(elements);

querySelectorAll方法返回的是一个静态的集合,也就是说,如果你在获取到元素之后,再向页面中添加或者删除元素,那么这个集合不会跟着改变。

需要注意的是,尽管querySelectorAll方法返回的NodeList是静态的,但是NodeList在有些情况下却是动态的,比如 Node.childNodes 返回的就是一个动态的NodeList。

总结成如下表格

方法 返回值类型 返回值个数 查不到时返回值 返回值状态:动态/静态
getElementById Element 1 null -
getElementsByClassName HTMLCollection(Array-like object) 0~n 空的HTMLCollection 动态
querySelector Element 1 null -
querySelectorAll NodeList(Array-like object) 0~n 空的NodeList 静态

References

getElementById
getElementsByClassName
querySelector
querySelectorAll

Array operations

Create array

Array constructor

ES5中Array的构造函数语法比较混乱,传入不同的值会得到不同的结果。

1
2
3
4
5
6
7
8
const a1 = new Array(1, 2);
console.log(a1); // [1, 2]

const a3 = new Array('2');
console.log(a3); // ['2']

const a2 = new Array(2);
console.log(a2); // [undefined, undefined], why?

通常情况下,Array的构造函数就是根据传入的参数初始化数组,只有一种情况例外:当且仅当传入一个number类型的参数时。此时,构造函数会创建一个指定长度的数组,而不是包含一个元素的数组。比如上面的数组a2,它的长度是2,且两个元素都是undefined,而不是一个包含数字2的数组。这种行为是不符合直觉的,

注意:在实际编程中,我们一般不使用Array构造函数来创建数组,而是使用数组字面量的方式[]来创建数组。

1
2
3
4
5
6
7
8
const a = [1, 2];
console.log(a); // [1, 2]

const b = ['2'];
console.log(b); // ['2']

const c = [2];
console.log(c); // [2]

为了解决Array constructor这种混乱,ES6引入了Array.ofArray.from方法。

Array.of

Array.of方法用于创建一个具有可变数量参数的数组实例,用户传入什么,就用什么创建数组,简单明了。

1
2
3
4
5
6
7
8
const a1 = Array.of(1, 2);
console.log(a1); // [1, 2]

const a2 = Array.of('2');
console.log(a2); // ['2']

const a3 = Array.of(2);
console.log(a3); // [2]

Array.from

既然有了Array.of,那么为啥还需要Array.from呢?Array.from主要是用于将其他类型转换为数组的。可以转换的类型有:

  • Array-like objects (arguments in function or NodeList from DOM)
  • Iterable objects (Set, Map, String, etc.)

Array-like objects

在ES6之前,把Array-like objects转换为数组大致有两种方式:

  1. 逐个元素复制,因为Array-like objects有length属性且可用下标迭代。
1
2
3
4
5
let arr = [];
for (let i = 0; i < arguments.length; i++) {
arr.push(args[i]);
}
return arr;
  1. 使用Array.prototype.slice.call(arguments),这种方式更简洁。
1
return Array.prototype.slice.call(arguments);

ES6之后,我们可以使用Array.from来转换Array-like objects为数组。
下面是一个求和函数,函数sum的参数是一个类数组对象,我们可以使用Array.from将其转换为数组,并累加求和。

1
2
3
4
5
// Array-like objects
function sum() {
return Array.from(arguments).reduce((acc, cur) => acc + cur, 0);
}
sum(1, 2, 3); // 6

当然,更好的写法是使用扩展运算符...,这样更简洁。

1
2
3
4
function sum(...args) {
return args.reduce((acc, cur) => acc + cur, 0);
}
sum(1, 2, 3); // 6

Iterable objects

下面是一个将字符串转换为数组的例子。

1
2
3
const str = 'hello';
const arr = Array.from(str);
console.log(arr); // ['h', 'e', 'l', 'l', 'o']

将Set转换为数组。

1
2
3
const set = new Set([1, 2, 3]);
const arr = Array.from(set);
console.log(arr); // [1, 2, 3]

将Map转换为数组。

1
2
3
const map = new Map([[1, 'one'], [2, 'two'], [3, 'three']]);
const arr = Array.from(map);
console.log(arr); // [[1, 'one'], [2, 'two'], [3, 'three']]

注意:Array.from方法的第二个参数是一个map函数,可以对数组中的每个元素进行处理。

1
2
const arr = Array.from([1, 2, 3], x => x * x);
console.log(arr); // [1, 4, 9]

Search in array

Please see this blog post: JavaScript Search in Array

插入,删除,替换数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
const numbers = [1, 2, 3, 4, 5];

// insert 6 at index 2
numbers.splice(2, 0, 6);
console.log(numbers); // [1, 2, 6, 3, 4, 5]

// remove 2 elements starting from index 3
numbers.splice(3, 2);
console.log(numbers); // [1, 2, 6, 5]

// replace 1 element at index 2 with 7
numbers.splice(2, 1, 7);
console.log(numbers); // [1, 2, 7, 5]

获取最后一个元素

使用length属性

1
2
3
const numbers = [1, 2, 3, 4, 5];
const result = numbers[numbers.length - 1];
console.log(result); // 5

使用slice方法

1
2
3
const numbers = [1, 2, 3, 4, 5];
const result = numbers.slice(-1)[0];
console.log(result); // 5

使用at方法:

1
2
3
const numbers = [1, 2, 3, 4, 5];
const result = numbers.at(-1);
console.log(result); // 5

原本JavaScript并不支持负值索引,但是有了at方法后,则可以使用负值索引。
注意at方法需要新版的浏览器和Node.js支持,详情看这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at

截取数组的一部分

使用slice方法

slice return a shallow copy of a portion of an array into a new array object selected from begin to end (not included). The original array will not be modified.

1
2
3
const numbers = [1, 2, 3, 4, 5];
const result = numbers.slice(1, 3);
console.log(result); // [2, 3]

合并两个数组

使用concat方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [6, 7, 8, 9, 10];
const result = numbers1.concat(numbers2);
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

使用扩展运算符

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [6, 7, 8, 9, 10];
const result = [...numbers1, ...numbers2];
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

数组去重

使用Set对象

1
2
3
const numbers = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
const result = [...new Set(numbers)];
console.log(result); // [1, 2, 3, 4, 5]

使用filter方法

这个方法比较巧妙,对于数组中的某个元素item,如果它在数组中的索引等于它第一次出现的索引,那么就保留它,否则就过滤掉。如果数组中有重复元素,在使用indexOf查找其下标时,会返回第一个出现的下标。这会导致indexOf(item) !== index,所以重复元素会被过滤掉。

1
2
3
const numbers = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
const result = numbers.filter((item, index) => numbers.indexOf(item) === index);
console.log(result); // [1, 2, 3, 4, 5]

使用reduce方法

1
2
3
const numbers = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
const result = numbers.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []);
console.log(result); // [1, 2, 3, 4, 5]

数组diff

使用filter方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = numbers1.filter(item => !numbers2.includes(item));
console.log(result); // [1, 2]

使用reduce方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = numbers1.reduce((acc, cur) => numbers2.includes(cur) ? acc : [...acc, cur], []);
console.log(result); // [1, 2]

数组交集

使用filter方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = numbers1.filter(item => numbers2.includes(item));
console.log(result); // [3, 4, 5]

使用reduce方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = numbers1.reduce((acc, cur) => numbers2.includes(cur) ? [...acc, cur] : acc, []);
console.log(result); // [3, 4, 5]

数组并集

使用Set对象

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = [...new Set([...numbers1, ...numbers2])];
console.log(result); // [1, 2, 3, 4, 5, 6, 7]

使用filter方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = [...numbers1, ...numbers2].filter((item, index, arr) => arr.indexOf(item) === index);
console.log(result); // [1, 2, 3, 4, 5, 6, 7]

使用reduce方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = [...numbers1, ...numbers2].reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []);
console.log(result); // [1, 2, 3, 4, 5, 6, 7]

数组差集

使用filter方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = [...numbers1, ...numbers2].filter(item => !numbers1.includes(item) || !numbers2.includes(item));
console.log(result); // [1, 2, 6, 7]

使用reduce方法

1
2
3
4
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [3, 4, 5, 6, 7];
const result = [...numbers1, ...numbers2].reduce((acc, cur) => numbers1.includes(cur) && numbers2.includes(cur) ? acc : [...acc, cur], []);
console.log(result); // [1, 2, 6, 7]

打平数组

使用flat方法

1
2
3
4
5
6
7
8
9
10
11
const numbers = [1, [2, 3], [4, [5, 6, [7, 8]]]];

const result1 = numbers.flat(); // 默认只展开一层
console.log(result1); // [ 1, 2, 3, 4, [ 5, 6, [ 7, 8 ] ] ];

const result2 = numbers.flat(2); // 展开两层
console.log(result2); // [1, 2, 3, 4, 5, 6, [7, 8]];

const result = numbers.flat(Infinity); // Infinity 表示展开所有层级
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8]

Integrate Jest to Angular App

  1. Create your angular app
  2. Install jest
    npm install jest jest-preset-angular @types/jest
  3. In your project root, create a setup-jest.ts file with the following contents:
    import 'jest-preset-angular/setup-jest';
  4. Create the jest.config.js file in your project root directory with the following contents:
    1
    2
    3
    4
    5
    module.exports = {
    preset: 'jest-preset-angular',
    setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
    globalSetup: 'jest-preset-angular/global-setup',
    };
  5. Adjust your tsconfig.spec.json in your project root
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "extends": "./tsconfig.json",
    "compilerOptions": {
    "outDir": "./out-tsc/spec",
    "module": "CommonJs",
    "types": ["jest"]
    },
    "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
    };
  6. update package.json file in your project root as below.
    1
    2
    "test": "jest --verbose",
    "test:watch": "jest --watch"
  7. Open your terminal and run npm run test, enjoy!
  8. If you want to remove karma and Jasmine and switch to Jest completely, run the following command.
    1
    npm uninstall karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter jasmine-core jasmine-spec-reporter

references:

xfive.co/blog/testing-angular-faster-jest/

for … of 功能

javascript中的for ... of语句用来遍历可迭代对象,比如遍历数组可以使用如下代码:

1
2
3
4
5
const nums = [1, 2, 3, 4, 5];
for (const num of nums) {
console.log(num);
}

可迭代对象有很多,包括以下这些:

  1. Array
  2. String
  3. TypeArray
  4. Map
  5. Set
  6. NodeList(and other DOM collections)
  7. Array like objects(arguments)
  8. generators produced by generator functions
  9. User-defined iterables.

for … of 执行原理

for ... of首先会调用可迭代对象的@@iterator()方法获取一个iterator,然后反复调用这个iteratornext方法来获取对应的值。

for … of 何时结束迭代?

以下三种情况都会导致for … of结束迭代。

  1. 遇到break语句
  2. 遇到continue语句
  3. iteratornext方法返回 done: true时(也就是正常遍历结束)

for … of 不会修改被迭代的对象

for … of底层使用generator实现,每次迭代都会生成一个新的变量,所以不会改变被迭代的对象。下面的代码将数组中每个值加1,并不会改变原来的数组,因为变量num时迭代时生成的,而且每次迭代就重新生成一个。

1
2
3
4
5
6
7
const nums = [1, 2, 3];
for (let num of nums) {
num += 1;
console.log(num); // 2, 3, 4
}
console.log(nums); // still [1, 2, 3]

for … of 中可以使用destruction

1
2
3
4
5
6
7
8
9
10
const students = [
{ name: 'Alice', age: 20 },
{ name: 'Bob', age: 21 },
{ name: 'Cindy', age: 22 },
];

for (const { name, age } of students) {
console.log(name, age);
}

何时使用经典的for循环?

所谓经典for循环就是使用下标遍历的循环,类似C语言中的for循环。

1
2
3
4
const nums = [1, 2, 3];
for (let i = 0; i < nums.length; i++) {
console.log(nums[i]);
}

是否使用经典的for循环有一个简单的判断标准,在遍历的过程中是否要改变原数组,如果需要,就使用,否则就可以使用其他循环代替,比如for ... of, forEach, filter, map, reduce等。

for…of vs for…in

for ... offor ... in都是用来遍历对象的,但是有一些区别:

  1. for ... of遍历的是可迭代对象,而for ... in遍历的是可枚举对象。
  2. for ... of遍历的是值,而for ... in遍历的是键。

遍历Array

对于数组来说,for ... of遍历的是数组的值,而for ... in遍历的是数组的键(下标)。

1
2
3
4
5
6
7
8
9
10
const nums = [1, 2, 3];
nums.foo = 'bar';

for (const num of nums) {
console.log(num); // 1, 2, 3
}

for (const key in nums) {
console.log(key); // 0, 1, 2, foo
}

遍历Object

在对象上使用for ... of会报错,因为对象不是可迭代对象,但是可以使用for ... in来遍历对象的键。

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
foo: "bar",
baz: "qux",
};

for (const value of obj) {
console.log(num); // TypeError: obj is not iterable
}

for (const key in obj) {
console.log(key); // 0, 1, 2, foo
}

使用for...of可以对Object.keys或者Object.values来进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
foo: "bar",
baz: "qux",
};

for (const key of Object.keys(obj)) {
console.log(key); // foo, baz
}

for (const value of Object.values(obj)) {
console.log(value); // bar, qux
}

参考

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

Android dependencies conflict resolve

今天在编译Android应用“草书字典”时突然发现以下错误:

1
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-18.0 (com.google.guava:guava:18.0) and jetified-listenablefuture-1.0 (com.google.guava:listenablefuture:1.0)

大意是说com.google.common.util.concurrent.ListenableFuture这个class出现在不同的package中,属于重复依赖,gradle无法处理,要手动处理一下。

app.gradle文件中加入如下一行就好了。

1
implementation 'com.google.guava:guava:27.0.1-android'

Hexo Usage

How to Write a new post

  1. Generate new post in your terminal: hexo new "Your post name"
  2. Open your project by vscode, then open file: source\_post\Your post name.md
  3. Edit your post with vscode, hexo support markdown and ejs files

Clean cache

  1. After finish editing, you can type hexo clean in your terminal to clean the cache, clean is short for clean here.
1
hexo clean

Generate static files

  1. After finish editing, you can type hexo g in your terminal to generate static files, g is short for generate here.
1
hexo g

Publish your post

  1. Type hexo d to deploy your post to github.io, d is short for deploy here.
1
hexo d

View your post

  1. open zdd.github.io to see your post, good job!

you can use npm run deploy to combine generate and deploy in a single command.

View post locally

  1. Run hexo s in terminal, then open localhost:4000 to see your post, this is very convenient to check your post before publish.
1
hexo s

404 File not found

When you encounter the 404 error, make sure to do the following

  1. Check your source\_post\Your post name.md file name, make sure it is the same as the title in the file.
  2. run hexo clean to clean the cache
  3. run hexo g to generate static files
  4. run hexo d to deploy your post to github.io

Tags not working

  1. Make sure you have a tags folder under source folder
  2. Install easy tag plugin by npm install hexo-easy-tags-plugin --save
  3. Delete .deploy_git folder
  4. Run hexo clean to clean the cache
  5. Run hexo g to generate static files
  6. Run hexo d to deploy your post to github.io
  7. Force refresh your browser by Ctrl + F5

Make sure tags config in themes\next\_config.yml is correct, remember to set amount to a large number

1
2
3
4
5
6
7
tagcloud:
# All values below are same as default, change them by yourself.
min: 12 # Minimun font size in px
max: 30 # Maxium font size in px
start: "#ccc" # Start color (hex, rgba, hsla or color keywords)
end: "#111" # End color (hex, rgba, hsla or color keywords)
amount: 2000 # <--------------- Set this to a large number

How to add tags

  1. Run hexo new post "tags"
  2. Make sure tags/index.md has the following content
    1
    2
    3
    4
    5
    ---
    title: tags
    date: 2023-06-30 23:21:34
    type: "tags"
    ---

Add multiple tags for a post

  1. Add tags in the front matter of your post, for example:
    1
    2
    3
    4
    5
    6
    7
    ---
    title: Hexo Usage
    date: 2023-04-16 22:03:56
    tags:
    - hexo
    - blog
    ---

Insert Read more tag to post

1. Insert `<!-- more -->` in your post, for example:
   
1
2
3
4
5
6
7
8
9
10
11
12
---
title: Hexo Usage
date: 2023-04-16 22:03:56
tags:
- hexo
- blog
---

# Hexo Usage
## How to Write a new post
<!-- more -->
# other content

ng-template

顾名思义,ng-template是一个模板元素,通常与结构化指令ng-if, ng-for, ng-switch及模板变量配合使用.

ng-template配合ng-if

1
2
3
<ng-template [ngIf]="true">
<div>Hello, world!</div>
</ng-template>

在实际编程中,我们一般不用上面的写法,而是采用指令的简写形式,也就是用*ngIf代替[ngIf]

1
<div *ngIf="true">Hello, world!</div>

这段代码编译后的结果和第一段代码是相同的。关于指令的简写形式,请参考这篇。注意:在ng-template上使用指令的简写形式是无效的,必须使用属性绑定的方式,下面的代码无法正常工作。

1
2
3
<ng-template *ngIf="true">
<div>Hello, world!</div>
</ng-template>

作为elseBlock使用

假设有如下需求,当conditiontrue时,显示Hello, world!,否则显示Goodbye, world!, 该如何实现呢?
我们可以在ngIf指令后面添加else关键字,然后在else后面添加一个模板引用变量,然后定义一个ng-template并用该模版变量标识之,如下所示:

1
2
<div *ngIf="condition else otherTemplate">Hello, world!</div>
<ng-template #otherTemplate>Goodbye, world!</ng-template>

如果你使用的是Angular17及以上版本,那么你可以使用built-in control flow语法。

1
2
3
4
5
@if(condition) {
<div>Hello, world!</div>
} @else {
<div>Goodbye, world!</div>
}

If-then-else

如果两个分支对应的模板都很大,那么可以采用这种方式,使结构更清晰,代码更易读。

1
2
3
<div *ngIf="condition; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>Content to render when condition is true.</ng-template>
<ng-template #elseBlock>Content to render when condition is false.</ng-template>

同样的,如果你使用的是Angular17及以上版本,那么你可以使用built-in control flow语法。

1
2
3
4
5
@if(condition) {
<div>Hello, world!</div>
} @else {
<div>Goodbye, world!</div>
}

看到了吗,built-in control flow语法的可读性更强,更符合人类的思维方式。

References:

  1. Angular ng-template
  2. ng-container - https://zdd.github.io/2024/07/09/angular-ng-container/

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment