你可能不知道的5个JavaScript特性(骚操作)

1. 作用域

const a = 3;

switch (a) {
  case 1:
    const result = a * 2;
    console.log(result);
    break;
  case 2:
    const result = a * 2;
    console.log(result);
    break;
  case 3:
    const result = a * 2;
    console.log(result);
    break;
}

在switch的case中,它们并不是单独作用域,所以如果一旦重复声明变量则会报错:

Uncaught SyntaxError: Identifier 'result' has already been declared

在WebStorm中也会有错误提示:

image-20210311120544123

貌似在VSCode中没有对应的错误提示。

image-20210311120513271

2. in

判断一个对象中是否有某个属性,你会使用什么方法?

const person = {
  name: "张三",
  age: 18,
};

if (person.name) {
  console.log("person.name 名字存在");
} else {
  console.log("person.name 名字不存在");
}

// 输出:person.name 名字存在

还是:

if ("name" in person) {
  console.log("name in person 名字存在");
} else {
  console.log("name in person 名字不存在");
}

// 输出:name in person 名字存在

那么这两种方法有什么区别呢?

众所周知,在JavaScript中undefinednull在条件判断中会被判断为false,也正是因为JavaScript有这个特性,带来一些方便之处也带来一些不方便之处,我们可以看看上面的代码中当name的值为undefined,最终输出的结果是什么?

const person = {
  name: undefined,
  age: 18,
};

if (person.name) {
  console.log("person.name 名字存在");
} else {
  console.log("person.name 名字不存在");
}

// 输出:person.name 名字不存在

显然,在上面的代码中name是有值的,它的值就是undefined,我们希望它输出**”name有值”**这个结果,但是使用第一种方法它默认判断person.name=false,所以也就无法进行输出。

而使用"name" in person这种语法,就可以正确的进行条件判断。同理namenull值的时候也一样。

const person = {
  name: undefined,
  age: 18,
};

if ("name" in person) {
  console.log("name in person 名字存在");
} else {
  console.log("name in person 名字不存在");
}

// 输出:name in person 名字存在

3. 模板语法

所谓的模板语法就是如下所示:

const name = "张三";
const age = 18;

console.log(`${name}今年${age}岁`);

// 输出:张三今年18岁

但是该模板语法还可以进行骚操作。

function introduction(strings, ...values) {
  // 这里返回的是什么,模板字符串的结果就是什么
  return values;
}

const name = "张三";
const age = 18;

console.log(introduction`${name}今年${age}岁`); // 输出:["张三", 18]
  • strings:模板中的普通字符串合成的数组。
  • …values:模板${}中变量合成的数组。

知道这个特性后可以操作的空间就多了,剩下的留给大家自己发挥。

4. Generator

async await的实现原理就是使用了Generator和Promise,Generator函数现在已经不常用了(反正我是不常用),不过在React中,有一个库叫做Redux-saga,它就使用到了Generator。

Generator的作用是让函数分步执行,当你调用一个Generator函数时,函数并不会立即执行而是返回一个这个生成器的 迭代器( iterator )对象。当这个迭代器的next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

可能看起来很懵,我们直接举一个例子来看一眼就大致明白了:

function* initID() {
  let id = 0;
  while (true) {
    yield id;
    id++;
  }
}

const getId = initID();
console.log(getId.next().value); // 0
console.log(getId.next().value); // 1
console.log(getId.next().value); // 2
console.log(getId.next().value); // 3
console.log(getId.next().value); // 4

这里还可以在next()中传入参数,如下所示:

function* initID() {
  let id = 0;
  while (true) {
    const x = yield id;
    console.log(x); // 从第二次开始,只有传入的那次才会获得值,其它都为undefined
    id++;
  }
}

const getId = initID();
console.log(getId.next());
console.log(getId.next(10));
console.log(getId.next());
console.log(getId.next());
console.log(getId.next());

5. 动态引入模块

现在浏览器已经支持了ES module引入模块的方式,但是仅限主流浏览器有效。

index.html的引入js文件的script标签中要添加type="module"属性。

<script src="./index.js" type="module"></script>

下面我们有一个模块module.js

console.log("这是默认加载的语句");

export default function module() {
  console.log("这是模块");
}

然后在index.js中进行调用:

import module from "./module.js";

module();

但是上面的导入方式有一个问题,就是当module.js这个文件比较大的话,可能会影响页面加载速度,带来不好的体验,于是就有了动态加载的方法,可以客户在有用到这个模块的时候才进行加载。

const a = Math.random() * 10;

// a小于5时才进行加载
if (a < 5) {
  import("./module.js").then((res) => {
    res.default();
  });
} else {
  console.log("不加载");
}

5.1 兼容性

image-20210310165157746

不得不说IE真是业界毒瘤

说到这里就不得不提到Vite,它的原理就是使用了type="module",然后在现代浏览器上面开发,所以在开发环境下的启动和热更新速度就非常快。

在webpack中也有类似的语法,一旦在webpack中使用了动态引入,则被引入的模块会被进行单独打包,其中React和Vue的页面懒加载也是使用了该原理

6. 最后

其实JavaScript还有更多的有趣的特性我们并不知道,编程本来就是一个积累过程,积累的越多,就更容易看懂别人代码中的一些高级写法。

参考链接: