0%

如何给 Checkbox 添加自定义样式?

各位老铁大家好,又是一个风和日丽的日子,人生苦短,不能总是留恋美好的风景,亦要时时刻刻学习,所谓活到老学到老。今天我们一起来学习一下如何给 html 中的 ckeckbox 添加自定义样式。

如何在 html 中实现 checkbox?

其实,html 中根本没有checkbox这种控件,也就是说,你不能这样写:

Read more »

今天我们一起来学习一下html中的label标签。

什么是label?

现实生活中,标签无处不在,比如我们在超市买东西时,商品上都会贴有标签,标签上会标明商品的名称、成分、型号、价格等信息。

Web世界中也有自己的标签,那就是label,它的作用是为控件提供一个说明。以如下登录界面为例,这里的UsernamePassword就是label,他们标明右侧的输入框分别要输入用户名和密码。label标签的作用就是为控件提供一个标题以说明这个控件的作用。

html-tag-label-login

当然label标签不仅可以用于输入框(input),还可以用于下拉选择框(select),单选框(radio),复选框(checkbox)等控件。

用于下拉选择框

html中使用select + option来完成下拉框的功能。

1
2
3
4
5
6
7
8
<div style="padding: 16px; border-radius: 4px; border: 1px solid #ccc;">
<label for="country">Country:</label>
<select id="country" name="country">
<option value="china">China</option>
<option value="usa">USA</option>
<option value="uk">UK</option>
</select>
</div>

渲染结果如下:

用于单选框

对于单选框(radio button),如果要实现互斥效果(多个radio button只能选择其中一个),必须给这些radio button设置相同的name,比如下面两个radio button的name都设置为gender

1
2
3
4
5
6
7
8
9
<div style="padding: 16px; border-radius: 4px; border: 1px solid #ccc;">
<span>Gender: </span>
<label for="male">
<input type="radio" id="male" name="gender" value="male"> Male
</label>
<label for="female">
<input type="radio" id="female" name="gender" value="female"> Female
</label>
</div>

渲染结果如下:

Gender:

用于复选框

html代码如下:

1
2
3
4
<div style="padding: 16px; border-radius: 4px; border: 1px solid #ccc">
<label for="subscribe">Subscribe to newsletter:</label>
<input id="subscribe" name="subscribe" type="checkbox" />
</div>

渲染结果如下:

如何使用label标签

为控件添加label标签主要有两种方式,一种是使用for/id的方式,另一种是使用嵌套的方式。

使用for/id方式提供label标签

  1. 首先,定义控件时,为控件提供一个id属性。
  2. 然后,在label标签中添加for属性,指定其值为控件的id属性值。

下面的代码中,我们定义了一个文本框控件,并给它设置了id属性,其值为username,并为它提供了一个label标签,并设置其for属性的值为username。这样,label和控件就关联起来了。

1
2
<label for="username">Username:</label>
<input id="username" name="username" type="text" />

使用嵌套方式提供label标签

如果不想使用for/id的形式,那么可以直接用label标签包裹住对应的控件

1
2
3
<label>Username:
<input type="text" name="username">
</label>

无论使用哪种形式,都可以调整label和控件的先后顺序,对于input输入框来说,一般label在前面,控件在后面。而对于checkbox来说,一般是控件在前而label在后,此时我们可以按如下方式书写html。

1
2
<input type="checkbox" id="subscribe" name="subscribe">
<label for="subscribe">Subscribe to newsletter:</label>

或者

1
2
3
<label>
<input type="checkbox" name="subscribe"> Subscribe to newsletter
</label>

哪些控件可以使用label标签

大多数表单控件都可以使用label标签,包括但不限于:

  1. 文本框 - text box <input type='text'>
  2. 单选框 - radio button<input type='radio'>
  3. 复选框 - checkbox<input type='checkbox'>
  4. 下拉列表 - <select>
  5. 多行文本框 - <textarea>

label标签的好处

使用label标签有哪些好处呢?

  1. 增强页面的可访问性,label标签不仅从视觉上为控件提供了标题,也为屏幕阅读器提供了可读的信息,这使得有视力障碍的用户也能更好的理解页面内容。
  2. 提供用户体验,label标签扩大了控件的可点击区域,即使用户点击label标签也能触发控件的操作。- 以上面第一个例子username控件为例,即使用户点击label标签username,input控件也能获取焦点。

CSS Replaced Elements

在Web开发中,可替换元素是指如下一类元素:

  1. 内容来自外部资源。
  2. 内容定义在document之外。

这么说可能有点抽象,我们举一个例子,下面的html中通过img标签引入了一个图片:

1
<img src="https://example.com/image.jpg" alt="Example Image">

在这个例子中,img元素就是一个可替换元素,因为它的内容(图片)来自于外部资源(src属性指定的URL)。

Html中的可替换元素有如下这些:

  1. <img>:用于显示图像。
  2. <video>:用于显示视频。
  3. <iframe>:用于嵌入其他HTML文档。
  4. <embed>:用于嵌入外部内容,如PDF文件或Flash动画。
  5. <fencedframe>:用于嵌入其他HTML文档。

以下这些tag在某些情况下也可能是可替换元素:

  1. <audio>:用于显示音频。
  2. <canvas>:用于绘制图形。
  3. <object>:用于嵌入外部内容,如PDF文件或Flash动画。
  4. <input>:用于显示表单控件。(image input types only)

可替换元素的特点

  1. 有自己的固有尺寸和固有纵横比,比如对于一个image来说,这个image本身就有大小和纵横比。当然,多数情况下,我们会用CSS控制它们显示的大小。
  2. 不能使用:before:after伪元素。

Syntax

User optional chaining operator ? instead of &&

1
2
3
4
5
6
7
8
9
10
const user = {
name: 'John',
address: {
city: 'New York',
state: 'NY'
}
};

const city = user && user.address && user.address.city; // old way
const city = user?.address?.city; // new way

这种方式确实简单很多,但是处理Unit Test可能会麻烦一些。

Use nullish coalescing operator ?? instead of ||

1
2
const name = user.name || 'Guest'; // old way
const name = user.name ?? 'Guest'; // new way

XSS

XSS = Cross Site Scripting, 即跨站脚本攻击为了和CSS(层叠样式表)区分开来,所以叫XSS。这种攻击方式是黑客向目标站点注入恶意代码,当用户浏览网页对应的恶意代码就会执行以窃取用户的信息,最常见的就是cookie盗取。常见的XSS攻击有三种

存储型

存储型攻击如下图所示:
alt text

  1. 黑客发现目标网站的漏洞,并提交恶意代码到目标网站,这些恶意代码存储到目标网站的数据库中。
  2. 用户访问目标网站,目标网站从数据库中读取恶意代码并返回给用户。
  3. 用户浏览器执行恶意代码,黑客获取用户的信息。
  • 首先黑客必须先发现目标网站存在XSS漏洞,然后黑客提交恶意代码(通过留言,创建表单等方式-这是方式都是目标网站提供的功能,所有用户都能使用)到网站服务器,服务器把提交的内容存储到后代数据库。假设黑客提交了某个博客文章的留言,或者创建了某个音频网站的一个专辑。
  • 正常用户访问目标网站,看到了黑客在博客上的留言,或者黑客创建的音频专辑,这时候浏览器会执行这些恶意代码,黑客就能获取用户的信息。

反射型

Dom型

如何防范XSS攻击

输入验证

在软件开发届有一句名言:永远不要相信前端传递过来的数据,后端一定要对前端传入的数据进行验证,即使前端已经验证了,后端也要验证。只要这样,才能保证安全。

对输出进行转义

现在流行的前端框架都会对用户的输入进行转义,比如React或者Angular框架,如果用户输入<script>alert('hello')</script>,这个内容会被转义成&lt;script&gt;alert('hello')&lt;/script&gt;,这样就可以正常渲染,而不会被浏览器执行了。

对Cookies使用HttpOnly属性

标记为HttpOnly的cookie,浏览器只能通过HTTP协议访问,而不能通过JavaScript访问,这样就可以防范XSS攻击。

CPS

CPS = Content Security Policy, 即内容安全策略,这是一种安全策略,可以防范XSS攻击。CPS是一个HTTP头部,可以告诉浏览器只能加载指定的资源,比如只能加载指定的域名下的资源,或者只能加载指定的类型的资源。这样就可以防范XSS攻击。
CPS只是众多防范XSS攻击方式的一种,还有其他方法可以方法XSS攻击,比如output encoding, sanitization等。

关于CSP的详细内容,请看这里

CPS也不只用于防范XSS攻击,还可以防范其他攻击,比如:

  1. Prevent Clickjacking(点击劫持)
  2. manipulator-in-the-middle(中间人攻击)

XSRF

XSRF = Cross Site Request Forgery, 即开展请求伪造。与XSS攻击方式不同,这种方式需要黑客自己有一个恶意站点,然后诱导用户点击恶意链接跳转到黑客的恶意站点,黑客再根据用户的cookie和其他信息伪造一个请求发送给用户访问的正常站点。

  1. 目标站点存在XSRF漏洞。
  2. 黑客自己有一个恶意站点。
  3. 用户登录过目标站点,并且在浏览器上保持有该站点的登录状态
  4. 黑客诱导用户点击恶意链接,跳转到黑客的恶意站点。

如何防范XSRF攻击

充分利用Cookies的SameSite属性

验证请求的来源站点

使用CSRF Token


前端性能优化有很多方面,细节很多,最近面试也经常考到这个问题,现整理如下,以便日后查阅。

Http层面的优化:

  1. 减少HTTP请求次数, Http请求是很耗时的操作,減少HTTP请求次数是提高网站性能的一个重要手段。
  2. 使用缓存,缓存可以减少服务器的压力,提高网站的访问速度。
  3. 使用CDN,CDN是内容分发网络,可以加速网站的访问速度。
  4. 使用Gzip压缩,Gzip是一种压缩算法,可以减小文件的体积,提高网站的访问速度。
  5. 使用HTTP2,HTTP2是HTTP协议的新版本,可以提高网站的访问速度。

JS/css/html层面的优化:

  1. 使用lazy loading,对于首页不需要立即展示的组件,可以采用延迟加载。
  2. 代码压缩,代码压缩是一种优化技术,可以减小文件的体积,提高网站的访问速度。现在的打包工具比如webpack都自动开启了代码压缩。
  3. Reduce Bundle size,减小文件的体积,提高网站的访问速度。
    1. 使用Tree Shaking,Tree Shaking是一种优化技术,可以减小文件的体积,提高网站的访问速度。
    2. 使用Code Splitting,Code Splitting是一种优化技术,可以减小文件的体积,提高网站的访问速度。
  4. 复杂的客户端计算可以用web worker来处理,web worker的好处是可以在后台线程中运行脚本,不会影响主线程的运行。不影响浏览器的渲染。
  5. CSS优化
    1. CSS很小的话,可以采用内联的方式插入到html中,这样就不必额外下载一个css文件。
    2. CSS很大的话,则要独立出一个文件来,这样可以利用缓存机制,注意index.html一般是不缓存的。

图片优化:

  1. 使用webp格式,webp是一种新的图片格式,可以减小文件的体积,提高网站的访问速度。
  2. 使用图片懒加载,图片懒加载是一种优化技术,可以减小文件的体积,提高网站的访问速度。
  3. 使用图片压缩,图片压缩是一种优化技术,可以减小文件的体积,提高网站的访问速度。
  4. 雪碧图,雪碧图是一种优化技术,可以减小文件的体积,提高网站的访问速度。

概述

Zone是Angular中非常重要的一个概念,简单来说,Zone就是一个execution context(执行上下文),这个执行上下文可以存在于多个JS虚拟机的执行轮次中。这意味着它可以在异步操作中保持状态,而不受 JavaScript 单线程执行模型的影响。它的作用主要有以下几点:

  1. 捕获异步操作并添加额外的功能以实现自动变更检测。
  2. 应用程序性能分析(profiling)
  3. 跟踪跨多个 VM 轮次的长堆栈跟踪(long stack traces),对于调试复杂的异步代码非常有用。

今天来看看在JavaScript中如何控制对象的可访问性。

不可扩展对象

使用Object.preventExtensions()方法可以将一个对象设置为不可扩展,不可扩展的对象不能添加新属性,但可以修改或者删除已有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"use strict"; // eslint-disable-line

const person1 = {
name: 'zdd',
};
console.log(Object.isExtensible(person1)); // true;

Object.preventExtensions(person1);
console.log(Object.isExtensible(person1)); // false

person1.name = 'Philip'; // OK
console.log(person1.name); // Philip

delete person1.name; // OK;
console.log(person1.name); // undefined.

person1.age = 18; // Error: Cannot add property age, object is not extensible

密封对象

使用Object.seal()方法可以将一个对象密封,密封后的对象不能添加、删除属性,但可以修改属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict"; // eslint-disable-line

const person1 = {
name: 'zdd',
};
console.log(Object.isSealed(person1)); // false

Object.seal(person1);
console.log(Object.isSealed(person1)); // true

person1.name = 'Philip'; // OK
console.log(person1.name); // Philip

person1.age = 18; // Error, Seal object is non-extensible.
delete person1.name; // Error in strict mode.

冻结对象

使用Object.freeze()方法可以冻结一个对象,冻结后的对象不能添加、修改、删除属性,也不能修改属性的可枚举性、可配置性、可写性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';

const person1 = {
name: 'zdd',
};
console.log(Object.isFrozen(person1)); // false

Object.freeze(person1);
console.log(Object.isFrozen(person1)); // true

person1.name = 'ddz'; // Error. object is frozen.
person1.age = 18; // Cannot add property age, object is not extensible
delete person1.name; // TypeError: Cannot delete property 'name' of #<Object>

console.log(person1);

总结

特性 不可扩展对象 (Object.preventExtensions) 密封对象 (Object.seal) 冻结对象 (Object.freeze)
是否可新增属性 ❌ 不可新增 ❌ 不可新增 ❌ 不可新增
是否可修改属性值 ✅ 可修改 ✅ 可修改 ❌ 不可修改
是否可删除属性 ✅ 可删除 ❌ 不可删除 ❌ 不可删除
是否可更改原型链 ✅ 可更改 ✅ 可更改 ❌ 不可更改
是否可更改可枚举性 ✅ 可更改 ❌ 不可更改 ❌ 不可更改
是否可更改可配置性 ✅ 可更改 ❌ 不可更改 ❌ 不可更改
是否可更改可写性 ✅ 可更改 ✅ 可更改 ❌ 不可更改

今天我们来学习一下如何在JavaScript中实现原型继承,话不多说,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 父类的构造函数
function Animal(name) {
this.name = name;
}

// 父类的方法
Animal.prototype.printName = function () {
console.log(this.name);
};

// 子类构造函数
function Dog(name, category) {
// 好用父类构造函数,初始化父类属性。
Animal.call(this, name);

// 初始化子类的属性
this.category = category;
}

// 设置 Dog 的原型为 Animal 的实例,建立原型链
Dog.prototype = Object.create(Animal.prototype);

// 修复子类的 constructor 指向(上面一行代码已经将constructor指向了Animal)
Dog.prototype.constructor = Dog;

// 子类独有的方法。
Dog.prototype.printCategory = function () {
console.log(`Dog name: ${this.name} barking`);
};

const dog = new Dog('dog', 'Pet');
dog.printName(); // 调用父类的方法
dog.printCategory(); // 调用自己的方法

今天在HackerRank上做Angular题目,偶然遇到一个循环固定次数的问题,这里记录一下。

假设需要在template里面循环count次,每次生成一个div,而count是后台api返回的一个变量,该如何实现呢?

方法一,使用数组

我能想到的最简单的方法,就是定义一个含有count个元素的数组,然后使用ngFor遍历这个数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export class AppComponent implements OnInit {
nums: number[] = [];

ngOnInit() {
this.fetchData().then(count => {
// 创建含有count个元素的数组,数组元素的值无所谓,我们只用到元素个数。
this.nums = Array(count).fill(0);
})
}

// 模拟后台api返回的count
fetchData() {
// Random int between 0 - 10
const randomInt = Math.floor((Math.random() * 10))
return Promise.resolve(randomInt);
}
}

然后使用ngFor遍历这个数组。这样会循环生成count个div元素。

1
2
3
<ng-container *ngFor="let n of nums; let i = index">
<div>{{i}}</div>
</ng-container>

方法二,在模板中构造数组

1
2
3
<div *ngFor="let i of [].constructor(count).keys()">
第 {{i + 1}} 次循环 <!-- 显示从1开始的序号 -->
</div>

注意:该方法会导致一个错误:需要研究一下,为什么?

1
core.mjs:6662 ERROR RuntimeError: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '[object Array Iterator]'. Current value: '[object Array Iterator]'. Expression location: _AppComponent component. Find more at

方法三,使用自定义指令

使用时,需要在模块或者独立组件的imports中引入RepeatDirective,否则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 自定义指令代码
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
standalone: true,
selector: '[appRepeat]',
})
export class RepeatDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
) {}

@Input('appRepeat') set count(value: number) {
this.viewContainer.clear(); // 清空容器
for (let i = 0; i < value; i++) {
this.viewContainer.createEmbeddedView(this.templateRef, {
$implicit: i + 1, // 传递索引值
});
}
}
}

调用自定义指令

1
2
3
<div *appRepeat="count; let i">
这是第 {{ i }} 次循环
</div>

方法四:自定义管道

使用时,需要在模块或者独立组件的imports中引入RangePipe,否则会报错。

1
2
3
4
5
6
7
8
9
10
11
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
standalone: true,
name: 'range'
})
export class RangePipe implements PipeTransform {
transform(value: number): number[] {
return Array.from({ length: value }, (_, i) => i);
}
}

使用自定义管道

1
2
3
<div *ngFor="let i of count | range">
第 {{i + 1}} 次循环 <!-- 显示从1开始的序号 -->
</div>