读《重学前端》

212 阅读5分钟

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系统的进程可以通过任务管理器看到进程。

image.png

  • 进程是 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 遵循了一般编程语言的“语句 - 表达式”结构,多数编程语言都是这样设计的。

image.png

image.png

这里记录一下自己以前不太熟悉的知识点。

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

宏任务中包含了微任务,一个宏任务执行完成,开始下一个宏任务。 image.png

就拿上面代码来说: 0. 开始执行,发现了一些代码,放进一个宏任务中

  1. 先执行a
  2. 执行b
  3. 发现异步操作then,属于微任务,把他添加进当前宏任务中的微任务的队列
  4. 发现定时器d,setTimeout属于宏任务,添加到宏任务的队列
  5. 发现定时器e,setTimeout属于宏任务,添加到宏任务的队列
  6. 执行f
  7. 代码都已经执行完成,开始执行微任务队列,打印出c
  8. 微任务队列执行完成,开始执行下一个宏任务队列
  9. 直接打印出d,没有其他微任务,开始执行下一个宏任务队列
  10. 打印出e,所有宏任务执行完成

数据类型

JavaScript 语言的每一个值都属于某一种数据类型。JavaScript 语言规定了 7 种语言类型。根据最新的语言标准,这 7 种语言类型是:

  • Undefined;
  • Null;
  • Boolean;
  • String;
  • Number;
  • Symbol;
  • Object。 程序逻辑由数据类型+运行算法组成。