前端冷知识

1、看起来一样的字符串却不相等

第一次遇到这种情况的同学可能会一脸懵,其实这主要是字符编码不同造成的
(这种情况可能发生在字符串来自不同地方的时候,如 文件的名称,html中的textContent,js等)

通过 encodeURIComponent 将两个字符串进行编码,你会发现编码结果不同:
console.log(str1, str2, encodeURIComponent(str1), encodeURIComponent(str2), str1 === str2);

经过测试,可以使用正则去掉空白类字符(也许尽管看起来没有空白字符)再比较,或者使用 localeCompare 比较两个字符串:str1.localeCompare(str2)===0

2、Storage的页面共享问题

通常,用户登录后会将token等登录信息缓存在sessionStorage中,这样用户关闭网页清理token,而刷新网页可以不用登录。似乎很完美。但:(注:以下新页面均是同源页面)

window.open()  打开新的页面;a标签 跳转新页面;ctrl + 鼠标右键点击 打开新页面。
这三种情况,新页面会复制已有的sessionStorage——但仅仅是复制,如果原页面sessionStorage变了,新页面是不会同步变的。那么,如果用户在原页面退出了登录,新页面却还保留了token(甚至有可能是合法的token),是不是很糟糕!

鼠标右键点击连接,弹出上下文菜单,再点击“新窗口(或标签页)打开链接”,打开新页面; 复制原页面网址,打开一个新的空的标签页,然后粘贴此网址,打开新页面。
这两种情况,sessionStorage不会被复制。也就是你需要再新页面重新登录!
另外Electron 将前端代码打包成桌面应用,window.open打开新标签页,也不会被复制。这是更糟糕的情况。

采用 localStorage,这是所有页面共享的,无论以何种方式打开的不同页面,无论以何种方式修改了localStorage,所有页面都会同步变化。
你可测试,几乎所有的知名网站 用户登录信息都是所有页面同步共享的(这个页面切换用户了,另一个页面也会变化),所以不要再用sessionStorage保存用户信息了!!

注意localStorage和sessionStorage的这个区别,将有助于你更好的做决策。

3、Map 和 Object互转问题

Map类型 和 普通Object 类型,都是键值对的形式,他们之间需要互转的场景也很多。如果你还在用for循环互转,那么这些技巧能帮你简化代码:

  • 键值对entry,实际上就是一个  [ key , value ] 这样的数组
  • new Map( iterableEntries ) 可以直接创建一个map,传入的参数是每个元素都是entry的可迭代对象,如数组,set,其他map 等。
  • Object.fromEntries( iterableEntries ) 可以直接创建一个对象。传入的参数和上面的完全一致
  • Object.entries(obj)  和 map.entries() 返回都是可迭代对象(每个元素都是entry的

于是就可以很优雅的实现两者的互转:

// map 转 obj:
const obj = Object.fromEntries(map);
const obj = Object.fromEntries(map.entries())
// obj 转 map: const map = new Map(Object.entries(obj));

以及更多其他的互转:

// 数组 转 map或obj
const arr = [['a',1],['b',2],['c',3]]
const map = new Map(arr)
const obj = Object.fromEntries(arr)

// for of 循环的解构赋值
for(const [k,v] of map){
    console.log(k,v)
}

4、window.parent

当前窗口的父窗口对象(如果没有父窗口,就是自身。)

window.parent === window 表明当前窗口不是在 iframe object frame这些标签下【参考】,这在避免自己的网站被恶意引用(如劫持类的攻击)时很有用。

5、多重循环中的label

在循环语句中,有两个重要的控制语句 break 和 continue,表示 退出当前循环 和 跳过本次循环进行下一个。

在多重循环中,使用label 控制非常方便。如果你还在用一些中间flag来传递控制,知道这个技巧尤为重要。举个例子,还好感受一下吧:

loop1:
for (i = 0; i < 3; i++) {     
   loop2:
   for (j = 0; j < 3; j++) {  
      if (i == 1 && j == 1) {
         break loop1;       // 这里直接退出外层循环   
         // continue loop1;   // 这里退出内层循环,继续外层的下一条循环
      }
      console.log("i = " + i + ", j = " + j);
   }
}

6、try 语句块中的异步

直接上代码:

try {
    Promise.reject(new Error('test error'))
} catch (err) {
    console.log('catch eror:' + err.message)
} finally {
    console.log('finally')
}

这段代码,只会打印出 finally,然后提示异常未捕获。这里的try语句只能捕获同步的异常

(async function () {
    try {
        await Promise.reject(new Error('test error'))
    } catch (err) {
        console.log('catch eror:' + err.message)
    } finally {
        console.log('finally')
    }
})()

这段代码能和你期望的一样:先打印出catch eror:test error,再打印出 finally。能成功捕获异步异常!

7、?? 和 ?.

这两个都是判断一个变量是否存在,是否为nullish(null或undefined),但结果是相反的。??是nullish时为true,?.不是nullish时才为true:

 ?.  又叫可选链操作符;   ??  控制合并运算符;   ??=  逻辑空赋值。示例:

console.log(obj?.a)     // obj不是nullish时 打印obj.a,否则打印undefined
obj.dog?.['name']    // dog属性存在的话,取其name(注意写法,不是obj.dog?['name'], ?.是一体的),[]里面是表达式,表达式的结果作为key的name。
// 如果dog不存在,后面的表达式将不会计算,类似于if else 直接跳过!

obj.add?.(1,2,3)    // 有add这个方法的,就调用这个方法。注意 如果存在add,但add不是方法,这会报错。()表示作为方法来调用它。

// ?.不能用于赋值   obj?.name = 'Jack'  这是错误的!
let foo = null ?? 'default string';    // ?? 则表示是nullish时,取后面的结果否则取前面的结果!
const baz = 0 ?? 42;     // 结果0  (0 不是 nullish)
console.log( B() ?? C() );    // B()返回的不是undefined或null, C方法将不会调用!
(null || undefined ) ?? "foo"; // ||或&&或关系比较 都不能与??直接连用,需要借助括号     
       
a.duration ??= 10;    // a.duration是nullish时才赋值,等价于
a.duration??(a.duration=10) 

在复杂表达式中为了避免空值引起的异常,基本都用&&或||的技巧,但这任然很麻烦,而且无法很好的规避0,false,NaN这些合法的值,现在有了?和?? 让一起再次简单了起来! 

高级示例:

// 当变量age存在时(存在包括0,NaN的字符串),调用验证函数validateAge:
age?.[validateAge()]

// 当age不存在时,弹出提示:
age??alert("年龄不存在")

 8、基础类型添加属性的问题

基础类型如:number、string、boolean、bigint、symbol

通过new创建的Number、String、Boolean 是对象,所以可以添加任意的属性。

let a = 5
a.name="a"   // 严格模式下报错,非严格模式下静默失败!
console.log(a,a.name)

更多冷知识,正在收录中……

原文地址:https://www.cnblogs.com/zhwc-5w4/p/13087007.html