var
在let出现之前,一直使用var来声明变量,那个时候面试常问的一些问题诸如变量提升,for循环的坏处之类的。 现在虽然有了更可靠的let,因为还有老的代码存在,所以这里也记录下var所存在的问题。
变量污染的问题
for(var i = 0;i<10;i++){}
console.log(i); // 10
if(true) {
var b = 1;
}
console.log(b); // 1
像上面的代码,在for,if,或者switch等操作中,我们无意间会创建出某些变量,造成变量污染,这并不是我们理想的样子。
使用let来优化:
for(let i = 0;i<10;i++){}
console.log(i); // a is not defined
if(true) {
let b = 1;
}
console.log(b); // b is not defined
作用域考察问题
以前的一个经典的面试题,我被问到过好几次: 如何在不修改定时器的情况下打印出0-9?
for(var i = 0; i < 10; i++){
setTimeout(function(){
console.log(i);
}, 0)
}
// 这里是打印出10个10
-
想要0-9只需要把var改成let就可以了,var创建的是全局的变量,定时器中拿到的i已经是for循环叠加之后的i了,所以得到了10。let则是当前作用域下的变量,定时器中拿到的i是每次循环的i,所以得到了0-9。
-
创建一个封闭的作用域区间
for(var i = 0; i < 10; i++){
(function(a){
setTimeout(function(){
console.log(a); // 得到0-9
}, 0)
})(i);
};
通过自执行函数的封闭区间来实现类似let的效果,让i仅在当前作用域下生效。
==和===
js中存在==和===两种校验是否相等的运算符。
==代表值相等===代表值和类型都相等
由于双等号的存在,使用中会存在一些问题,例如:
undefined == null // true
false == '0' // true
true == 'true' // false
[] == 0 // true
[] == false // true
new Boolean('false') == false // false
重读前端中列出了双等号的转换方式:
- undefined 与 null 相等;
- 字符串和 Boolean 都会转为数字再比较;
- 对象转换成 primitive 类型再比较。
有时候不知道会拿到什么类型的数据时,为了严格的控制数据类型和可能存在的状况bug,还是建议用三等来判断,把拿到的不确定数据都进行类型转换之后再做比较。
语义化标签
HTML5的语义标签屡见不鲜了,但是平时用到的很少。常用的像:
h123456
p
img
a
aside
article
nav
header
footer
...等等
HTML称为超文本标记语言,也就是在原本的记事本文档的基础上添加了新的展示信息,让界面看起来更加的丰富和有层次感。
语义化标签的设计意图是想按照书籍的排版样式来规定使用的标签:
- 有序列表
- 无序列表
- 主标题
- 副标题
- 导航-菜单-目录
- 插入的图片-插入的链接
- 粗体-斜体
- 等等...
但其实我编码时大部分还是使用div、span、p等来布局。
下面引用一位其他读者的评论:
语义化标签的一些点可能会降低开发者的使用欲望
1.很多语义标签自带样式,而这些样式我们并不需要,所以还要先取消默认样式,麻烦。
2.现代网页已经不再是按照书籍排版的结构来的,很多页面元素并不容易确定应该使用哪个语义标签。
对于第二点我深以为然,我也并没有深入研究过语言文字排版,而且现在的界面设计相比书籍更加的丰富多样,很多地方根本不知道应当使用什么样式来排版,就一直用div、span来替代。
对于语义化标签的总结:用到合适的地方(最好) > 不用(也没错) > 乱用(算了吧)
作者说:实际上,HTML 这种语言,并不像严谨的编程语言一样,有一条非此即彼的线。一些语义的使用其实会带来争议,所以我的建议是:你可以尽量只用自己熟悉的语义标签,并且只在有把握的场景引入语义标签。这样,我们才能保证语义标签不被滥用,造成更多的问题。
线程和进程
window系统的进程可以通过任务管理器看到进程。
- 进程是 cpu 资源分配的最小单位
- 线程是 cpu 调度的最小单位,线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程
进程是一个工厂,工厂有它的独立资源-工厂之间相互独立-线程是工厂中的工人,多个工人协作完成任务-工厂内有一个或多个工人-工人之间共享空间 js是众所周知的单线程语言,意味着它只能做一个操作。
浏览器是多进程的,每开一个tab页就相当于创建了一份独立的进程。
写替换标签时
关于替换标签:加载成功之后,内容会替换原有的占位,类似img,video,audio等
关于img标签
如果从性能的角度考虑,建议给出图片的宽高,因为替换型元素加载完文件后,如果尺寸发生变换,会触发重排版。 img的alt属性建议给到,当图片加载失败时,当使用屏幕阅读器访问页面时,都能带来相对友好的交互。
响应式图片使用picture标签
<picture>
<source srcset="image-wide.png" media="(min-width: 600px)">
<source srcset="image-wide2.png" media="(min-width: 1200px)">
</picture>
相比于使用css的媒体查询替换图片来说,picture标签语义更加明确,结构也更清晰,如果需要兼容低版本浏览器,还是需要小心使用。
video/audio中使用source
video标签也支持src属性,但是仅能填一个地址,不同浏览器支持的视频格式不一致,使用source标签可以让浏览器选择他所支持的视频格式来加载。 同样,相比于使用js代码来判断浏览器厂商和版本,source标签目的更加明确,可读性更高。 浏览器支持相对良好:Internet Explorer 9+, Firefox, Opera, Chrome 以及 Safari 支持。
<video controls="controls" >
<source src="movie.webm" type="video/webm" >
<source src="movie.ogg" type="video/ogg" >
<source src="movie.mp4" type="video/mp4">
You browser does not support video.
</video>
刷新页面使用location.href
使用location.reload() 会刷新页面,刷新页面时页面所有资源(css,js,img等)会重新请求服务器;建议使用location.href=“当前页url” 代替location.reload() ,使用location.href 浏览器会读取本地缓存资源。
语句-表达式
JavaScript 遵循了一般编程语言的“语句 - 表达式”结构,多数编程语言都是这样设计的。
这里记录一下自己以前不太熟悉的知识点。
var语句
- 预编译阶段变量提升
console.log(a); // undefiend
var a = 1;
- 特性:穿透for,switch,if等
if(true) {
var a = 1;
}
console.log(a); // 1
let和const语句
- 仅作用于当前作用域,不可再次声明。
- const声明的变量无法修改。
- 无变量提升问题。
- 不会穿透for,switch,if等
函数声明
- 预编译阶段提升
console.log(foo); // function
function foo() {};
- 在if中不会提升
console.log(foo); // undefined
if(true) {
function foo() {};
}
IIFE-立即执行的函数表达式
- 用来产生作用域,例如:
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i); // 得到10个10
})
}
// 使用IIFE来得到0-9
for(var i = 0; i < 10; i++){
(function(a){
setTimeout(function(){
console.log(a); // 得到0-9
}, 0)
})(i);
};
- 产生只读的函数名特性
var a = 1;
(function a() {
console.log(a); // function
a = 2; // 在当前作用域中a作为函数名只读,无法修改
console.log(a); // function
})();
console.log(a); // 1;
事件循环-执行顺序
单线程
js是单线程的,意味着他同一时间只能做一件事,也就是说所有的任务代码需要排队来执行,一个任务完成之后才能开始下一个任务。 任务的排队被称作消息队列。 重复执行消息队列中的任务的这一操作被称作事件循环。
我们的js代码从上往下执行,但是难免会有一些异步操作,例如发起ajax请求,使用定时器操作。那么遇到异步操作时js是怎么处理的呢?
来看下以下代码:
console.log("a")
let r = new Promise(function (resolve, reject) {
console.log("b");
resolve()
});
r.then(() => console.log("c"));
setTimeout(() => { console.log("d") }, 0)
setTimeout(() => { console.log("e") }, 1000)
console.log("f")
如果不能在短时间得出准确的结论,那么就需要深入了解js中的事件执行机制了。
宏任务和微任务
对于消息队列中的任务,分为两类:
- 宏任务(macrotask ):setTimeout、setInterval
- 微任务(microtask):promise
宏任务中包含了微任务,一个宏任务执行完成,开始下一个宏任务。
就拿上面代码来说: 0. 开始执行,发现了一些代码,放进一个宏任务中
- 先执行a
- 执行b
- 发现异步操作then,属于微任务,把他添加进当前宏任务中的微任务的队列
- 发现定时器d,setTimeout属于宏任务,添加到宏任务的队列
- 发现定时器e,setTimeout属于宏任务,添加到宏任务的队列
- 执行f
- 代码都已经执行完成,开始执行微任务队列,打印出c
- 微任务队列执行完成,开始执行下一个宏任务队列
- 直接打印出d,没有其他微任务,开始执行下一个宏任务队列
- 打印出e,所有宏任务执行完成
数据类型
JavaScript 语言的每一个值都属于某一种数据类型。JavaScript 语言规定了 7 种语言类型。根据最新的语言标准,这 7 种语言类型是:
- Undefined;
- Null;
- Boolean;
- String;
- Number;
- Symbol;
- Object。 程序逻辑由数据类型+运行算法组成。