【译】深入了解 JavaScript V8

thbcm阅读(258)

文章来源于公众号:符合预期的CoyPan ,作者CoyPan

原文标题:A Deep Dive Into V8

原文链接:blog.appsignal.com/2020/07/01/a-deep-dive-into-v8.html?utm_source=javascript-weekly-sponsored&utm_medium=email&utm_campaign=deep-dive-v8&utm_content=sponsored-link

正文开始

大部分前端开发人员都会遇到一个流行词:V8。它的流行程度很大一部分是因为它将 JavaScript 的性能提升到了一个新的水平。

是的,V8很快。但它是如何发挥它的魔力?为什么它反应如此迅速呢?

官方文档指出: V8 是谷歌开源高性能 JavaScriptWebAssembly 引擎,用 C++ 编写。它主要用在 ChromeNode.js中,等等。

换句话说,V8是一种C++开发的软件,它将 JavaScript 编译成可执行代码,即机器码。

现在,我们开始看得更清楚,ChromeNode.js只是一个桥梁,负责把 JS 代码运送到最终的目的地:在特定机器上运行的机器码。

V8性能的另一个重要角色是它的分代和超精确的垃圾收集器。它被优化为使用低内存收集 JavaScript 不再需要的对象。

除此之外,V8 还依靠一组其他的工具和特性来改进 JS 的一些固有功能。这些功能往往会使 JS 变慢(例如JS的动态特性)。

在本文中,我们将更详细地探讨这些工具(Ignition 和 TurboFan)和特性。除此之外,我们还将介绍V8的内部功能、编译和垃圾回收过程、单线程特性等基础知识。

从基础的开始

机器码是如何工作的呢?简单地说,机器代码是在机器内存的特定部分执行的一组非常低级的指令。

生成机器码的过程,用C++举例,大概像下面这样:

在进一步讨论之前,必须指出这是一个编译过程,它不同于 JavaScript 解释过程。实际上,编译器在进程结束时生成一个完整的程序,而解释器作为一个程序本身工作,它通过读取指令(通常是脚本,如JavaScript脚本)并将其转换为可执行命令来完成任务。

解释过程可以是动态的(解释器解析并只运行当前命令)或完全解析(即解释器在继续执行相应的机器指令之前首先完全翻译脚本)。

回到图中,编译过程通常从源代码开始。你实现代码,保存并运行。运行的进程依次从编译器开始。编译器是一个程序,和其他程序一样,运行在你的机器上。然后它遍历所有代码并生成对象文件。那些文件是机器代码。它们是在特定机器上运行的优化代码,这就是为什么当你从一个操作系统转移到另一个操作系统时必须使用特定的编译器。

但是你不能执行单独的对象文件,你需要把它们组合成一个文件,即众所周知的.exe文件(可执行文件)。这是Linker的工作。

最后,Loader是代理,负责将 exe 文件中的代码传输到操作系统的虚拟内存中。它基本上是一个运输工具。在这里,你的程序终于开始运行了。

听起来是一个漫长的过程,不是吗?

大多数时候(除非你是在银行大型机上使用汇编的开发人员),你会花时间用高级语言编程:Java、C#、Ruby、JavaScript等。

语言越高级,速度越慢。这就是为什么CC++速度更快,因为它们非常接近机器代码语言:汇编语言。

除了性能之外,V8的主要优点之一是超越ECMAScript标准的可能性,并且理解C++

JavaScript 仅限于ECMAScript。而V8引擎,为了存在,必须是兼容的,但不限于 JavaScript

具有将C++特性集成到V8中的能力是非常棒的。由于C++已经发展到非常好的 OS 操作的文件处理和内存/线程处理的特殊性——在 JavaScript 中拥有所有这些能力是非常有用的。

如果你仔细想想,Node.js它本身也是以类似的方式诞生的。它遵循与V8相似的路径,外加服务器和网络功能。

单线程

如果你是一个Node开发者,你应该很熟悉V8的单线程特性。一个 JS 执行上下文与线程数量成正比。

当然,V8在后台管理操作系统线程机制。它可以与多个线程一起工作,因为它是一个复杂的软件,可以同时执行许多任务。

但是,V8为每个 JavaScript 的执行上下文只创建一个单线程的环境。其余的都在V8的控制之下。

想象一下 JavaScript 代码应该进行的函数调用堆栈。 JavaScript 的工作原理是将一个函数堆叠在另一个函数之上,遵循每个函数的插入/调用顺序。在到达每个函数的内容之前,我们无法知道它是否调用其他函数。如果发生这种情况,那么被调用的函数将被放在堆栈中调用者的后面。

例如,当涉及回调时,它们被放在堆栈的末尾。

管理这个堆栈组织和进程所需的内存是V8的主要任务之一。

Ignition and TurboFan

自2017年5月发布的5.9版以来,V8 附带了一个新的JavaScript执行管道,它构建在V8的解释器Ignition之上。它还包括一个更新和更好的优化编译器-TurboFan

这些变化完全集中在整体性能上,以及 Google 开发人员在调整引擎以适应 JavaScript 领域带来的所有快速而显著的变化时所面临的困难。

从项目一开始,V8的维护人员就一直在担心如何在 JavaScript 不断发展的同时,找到一种提高V8性能的好方法。

现在,我们可以看到新引擎的Benchmarks测试结果,已经有了巨大提升:

Hidden Classes(隐藏类)

这是V8的另一个魔术。JavaScript 是一种动态语言。这意味着可以在执行期间添加、替换和删除新属性。例如,在Java这样的语言中,这是不可能的,在Java中,所有的东西(类、方法、对象和变量)都必须在程序执行之前定义,并且在应用程序启动后不能动态更改。

由于它的特殊性质,JavaScript 解释器通常基于散列函数(hash算法)执行字典查找,以准确地知道这个变量或对象在内存中的分配位置。

这对最后一道工序来说代价很大。在其他语言中,当对象被创建时,它们接收一个地址(指针)作为其隐式属性之一。这样,我们就可以准确地知道它们在内存中的位置以及要分配多少空间。

对于 JavaScript,这是不可能的,因为我们无法映射出不存在的内容。这就是Hidden Classes发挥作用的地方。

隐藏类与Java中的类几乎相同:静态类和固定类具有唯一的地址来定位它们。然而,V8并不是在程序执行之前执行,而是在运行过程中,每次对象结构发生“动态变化”时执行。

让我们看一个例子来说明问题。考虑以下代码片段:

function User(name, fone, address) {
   this.name = name
   this.phone = phone
   this.address = address
}

在 JavaScript 基于原型的特性中,每次实例化一个新的用户对象时,假设:

var user = new User("John May", "+1 (555) 555-1234", "123 3rd Ave")

然后V8创建一个新的隐藏类。我们称之为_User0

每个对象在内存中都有一个对其类表示的引用。它是类指针。此时,由于我们刚刚实例化了一个新对象,所以在内存中只创建了一个隐藏类。现在是空的。

当你在这个函数中执行第一行代码时,将在上一个基础上创建一个新的隐藏类,这次是_User1

它基本上是具有name属性的User的内存地址。在我们的示例中,我们没有使用仅将name作为属性的user,但每次这样做时,这就是V8将作为引用加载的隐藏类。

name属性被添加到内存缓冲区的偏移量 0,这意味着这将被视为最后顺序中的第一个属性。

V8还将向_User0隐藏类添加一个转换值。这有助于解释器理解:每次向User对象添加name属性时,必须处理从_User0_User1的转换。

当调用函数中的第二行时,同样的过程再次发生,并创建一个新的隐藏类:

你可以看到隐藏类跟踪堆栈。在由转换值维护的链中,一个隐藏类通向另一个。

属性添加的顺序决定了V8将要创建多少个隐藏类。如果您更改我们所创建的代码段中的行的顺序,那么也将创建不同的隐藏类。这就是为什么有些开发人员试图保持重用隐藏类的顺序,从而减少开销。

Inline Caching(内联缓存)

这是JIT(Just-in-Time)编译器中非常常见的一个术语。它与隐藏类的概念直接相关。

例如,每当你调用一个函数,将一个对象作为参数传递时,V8会看到这个动作,然后想:“嗯,这个对象作为参数成功地传递了两次或更多次……为什么不把它存储在我的缓存中以备将来调用,而不是再次执行整个耗时的隐藏类验证过程?”

让我们回顾上一个例子:

function User(name, fone, address) { // Hidden class _User0
   this.name = name // Hidden class _User1
   this.phone = phone // Hidden class _User2
   this.address = address // Hidden class _User3
}

当我们将 User 对象的实例两次作为参数传递给函数后,V8将跳转到隐藏类查找并直接转到偏移量的属性。这要快得多。

但是,请记住,如果更改函数中任何属性赋值的顺序,则会导致不同的隐藏类,因此V8将无法使用内联缓存功能。

这是一个很好的例子,说明开发人员不应该避免更深入地了解引擎。相反,拥有这些知识将有助于代码更好地执行。

Garbage Collecting(垃圾回收)

你还记得我们提到过V8在另一个线程中收集内存垃圾吗?这很有帮助,因为我们的程序执行不会受到影响。

V8使用众所周知的“标记和扫描”策略来收集内存中的旧对象。在这种策略中,GC扫描内存对象以“标记”它们以进行收集的阶段有点慢,因为这需要暂停代码执行。

但是,V8是递增的,也就是说,对于每个 GC 停顿,V8尝试标记尽可能多的对象。它使一切变得更快,因为在集合完成之前不需要停止整个执行。在大型应用程序中,性能的提高有很大的不同。

以上就是W3Cschool编程狮关于【译】深入了解JavaScript V8的相关介绍了,希望对大家有所帮助。

如何写出比别人更优秀的Vue?

thbcm阅读(209)

文章来源于公众号:前端人

对大部分人来说,掌握 Vue.js 基本的几个 API 后就已经能够正常地开发前端网站。但如果你想更加高效地使用 Vue 来开发,成为 Vue.js 大师,那下面我要传授的这五招你一定得认真学习一下了。在面试过程很多HR会问到。

第一招:化繁为简的Watch

场景还原:

组件创建的时候我们获取一次列表,同时监听input框,每当发生变化的时候重新获取一次筛选后的列表这个场景很常见,有没有办法优化一下呢?

招式解析:

首先,在watch中,可以直接使用函数的字面量名称;其次,声明immediate:true表示创建组件时立马执行一次。

第二招:一劳永逸的组件注册

场景还原:

我们写了一堆基础UI组件,然后每次我们需要使用这些组件的时候,都得先import,然后声明components,很繁琐!秉持能偷懒就偷懒的原则,我们要想办法优化!

招式解析:

我们需要借助一下神器webpack,使用 require.context() 方法来创建自己的(模块)上下文,从而实现自动动态require组件。这个方法需要3个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式。

我们在components文件夹添加一个叫global.js的文件,在这个文件里借助webpack动态将需要的基础组件统统打包进来。

最后我们在main.jsimport 'components/global.js',然后我们就可以随时随地使用这些基础组件,无需手动引入了。

第三招:釜底抽薪的router key

场景还原:

下面这个场景真的是伤透了很多程序员的心…先默认大家用的是Vue-router来实现路由的控制。

假设我们在写一个博客网站,需求是从/post-page/a,跳转到/post-page/b。然后我们惊人的发现,页面跳转后数据竟然没更新?!原因是vue-router“智能地”发现这是同一个组件,然后它就决定要复用这个组件,所以你在created函数里写的方法压根就没执行。通常的解决方案是监听$route的变化来初始化数据,如下:

bug是解决了,可每次这么写也太不优雅了吧?秉持着能偷懒则偷懒的原则,我们希望代码这样写:

招式解析:

那要怎么样才能实现这样的效果呢,答案是给router-view添加一个uniquekey,这样即使是公用组件,只要url变化了,就一定会重新创建这个组件。(虽然损失了一丢丢性能,但避免了无限的bug)。同时,注意我将key直接设置为路由的完整路径,一举两得。

第四招: 无所不能的render函数

场景还原:

vue要求每一个组件都只能有一个根元素,当你有多个根元素时,vue就会给你报错

招式解析:

那有没有办法化解呢,答案是有的,只不过这时候我们需要使用render()函数来创建HTML,而不是template。其实用js来生成html的好处就是极度的灵活功能强大,而且你不需要去学习使用vue的那些功能有限的指令API,比如v-for, v-if。(reactjs就完全丢弃了template

第五招:无招胜有招的高阶组件

划重点:这一招威力无穷,请务必掌握

当我们写组件的时候,父子组件的通信很重要。通常我们都需要从父组件传递一系列的props到子组件,同时父组件监听子组件emit过来的一系列事件。举例子:

有下面几个优化点:

每一个从父组件传到子组件的props,我们都得在子组件的Props中显式的声明才能使用。这样一来,我们的子组件每次都需要申明一大堆props, 而类似placeholer这种dom原生的property我们其实完全可以直接从父传到子,无需声明。方法如下

$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (classstyle 除外)。当一个组件没有声明任何 prop时,这里会包含所有父作用域的绑定,并且可以通过 v-bind="$attrs" 传入内部组件——在创建更高层次的组件时非常有用。

注意到子组件的@focus=$emit('focus', $event)"其实什么都没做,只是把event传回给父组件而已,那其实和上面类似,我完全没必要显式地申明:

$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

需要注意的是,由于我们input并不是BaseInput这个组件的根节点,而默认情况下父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。所以我们需要设置inheritAttrs:false,这些默认行为将会被去掉, 以上两点的优化才能成功。

以上就是W3Cschool编程狮关于如何写出比别人更优秀的Vue?的相关介绍了,希望对大家有所帮助。

parallelStream的坑,不踩不知道,一踩吓一跳

thbcm阅读(204)

文章来源于公众号:小姐姐味道

很多同学喜欢使用lambda表达式,它允许你定义短小精悍的函数,体现你高超的编码水平。当然,这个功能在某些以代码行数来衡量工作量的公司来说,就比较吃亏一些。

比如下面的代码片段,让人阅读的时候就像是读诗一样。但是一旦用不好,也是会要命的。

List<Integer> transactionsIds =
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

这段代码有一个关键的函数,那就是stream。通过它,可以将一个普通的 list ,转化为流,然后就可以使用类似于管道的方式对 list 进行操作。总之,用过的都说好。

问题来了

假如我们把stream换成parallelStream,会发生什么情况?

根据字面上的意思,流会从串行 变成并行

既然是并行,那用屁股想一想,就知道这里面肯定会有线程安全问题。不过我们这里讨论的并不是要你使用线程安全的集合,这个话题太低级。现阶段,知道在线程不安全的环境中使用线程安全的集合,已经是一个基本的技能。

这次踩坑的地方,是并行流的性能问题。

我们用代码来说话。

下面的代码,开启了8个线程,这8个线程都在使用并行流进行数据计算。在执行的逻辑中,我们让每个任务都 sleep 1秒钟,这样就能够模拟一些 I/O 请求的耗时等待。

使用stream,程序会在30秒后返回,但我们期望程序能够在1秒多返回,因为它是并行流,得对得起这个称号。

测试发现,我们等了好久,任务才执行完毕。

static void paralleTest() {
    List<Integer> numbers = Arrays.asList(
            0, 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
    );
    final long begin = System.currentTimeMillis();
    numbers.parallelStream().map(k -> {
        try {
            Thread.sleep(1000);
            System.out.println((System.currentTimeMillis() - begin) + "ms => " + k + " \t" + Thread.currentThread());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return k;
    }).collect(Collectors.toList());
}


public static void main(String[] args) {
//    System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
    new Thread(() -> paralleTest()).start();
}

实际上,在不同的机器上执行,这段代码花费的时间都不一样。

既然是并行,那肯定得有个并行度。太低了,体现不到并行的能能力;太大了,又浪费了上下文切换的时间。我是很沮丧的发现,很多高级研发,将线程池的各种参数背的滚瓜烂熟,各种调优,竟然敢睁一只眼闭一只眼的在 I/O 密集型业务中用上parallelStream

要了解这个并行度,我们需要查看具体的构造方法。在ForkJoinPool类中找到这样的代码。

try {  // ignore exceptions in accessing/parsing properties
    String pp = System.getProperty
        ("java.util.concurrent.ForkJoinPool.common.parallelism");
    if (pp != null)
        parallelism = Integer.parseInt(pp);
    fac = (ForkJoinWorkerThreadFactory) newInstanceFromSystemProperty(
        "java.util.concurrent.ForkJoinPool.common.threadFactory");
    handler = (UncaughtExceptionHandler) newInstanceFromSystemProperty(
        "java.util.concurrent.ForkJoinPool.common.exceptionHandler");
} catch (Exception ignore) {
}


if (fac == null) {
    if (System.getSecurityManager() == null)
        fac = defaultForkJoinWorkerThreadFactory;
    else // use security-managed default
        fac = new InnocuousForkJoinWorkerThreadFactory();
}
if (parallelism < 0 && // default 1 less than #cores
    (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
    parallelism = 1;
if (parallelism > MAX_CAP)
    parallelism = MAX_CAP;

可以看到,并行度到底是多少,是由下面的参数来控制的。如果无法获取这个参数,则默认使用 CPU个数-1 的并行度。

可以看到,这个函数是为了计算密集型业务去设计的。如果你喂给它一大堆任务,它就会由并行执行退变成类似于串行的效果。

-Djava.util.concurrent.ForkJoinPool.common.parallelism=N

即使你使用-Djava.util.concurrent.ForkJoinPool.common.parallelism=N设置了一个初始值大小,它依然有问题。

因为,parallelism这个变量是 final 的,一旦设定,不允许修改。也就是说,上面的参数只会生效一次。

张三可能使用下面的代码,设置了并行度大小为20

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");

李四可能用同样的方式,设置了这个值为30。那实际在项目中用的是哪个值,那就得问 JVM 是怎么加载的类信息了。

这种方式并不太非常靠谱。

一种解决方式

我们可以通过提供外置的forkjoinpool,也就是改变提交方式,来实现不同类型的任务分离。

代码如下所示,通过显式的代码提交,即可实现任务分离。

ForkJoinPool pool = new ForkJoinPool(30);


final long begin = System.currentTimeMillis();
try {
    pool.submit(() ->
            numbers.parallelStream().map(k -> {
                try {
                    Thread.sleep(1000);
                    System.out.println((System.currentTimeMillis() - begin) + "ms => " + k + " \t" + Thread.currentThread());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return k;
            }).collect(Collectors.toList())).get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

这样,不同的场景,就可以拥有不同的并行度。这种方式和CountDownLatch有异曲同工之妙,我们需要手动管理资源。

使用了这种方式,代码量增加,已经和优雅关系不大了,不仅不优雅,而且丑的要命。白天鹅变成了丑小鸭,你还会爱它么?

以上就是W3Cschool编程狮关于parallelStream的坑,不踩不知道,一踩吓一跳的相关介绍了,希望对大家有所帮助。

19 个优雅的 JavaScript 简写技巧

thbcm阅读(188)

文章来源于公众号:JavaScript忍者秘籍 ,作者LuckDay

作为一名编程人员,总希望能有一些减少代码量的技巧,今天这篇文章就给大家带来19个 JavaScript 的简写技巧。

1.三元操作符

当想写if...else语句时,使用三元操作符来代替。

    const x = 20;
    let answer;
    if (x > 10) {
        answer = 'is greater';
    } else {
        answer = 'is lesser';
    }

    

简写:

const answer = x > 10 ? ‘is greater’ : ‘is lesser’;

也可以嵌套 if 语句:

const big = x > 10 ? ” greater 10″ : x

2.短路求值简写方式

当给一个变量分配另一个值时,想确定源始值不是 null, undefined 或空值。可以写撰写一个多重条件的 if 语句。

    if (variable1 !== null || variable1 !== undefined || variable1 !== '') {
         let variable2 = variable1;
    }

    

或者可以使用短路求值方法:

const variable2 = variable1 || ‘new’;

3.声明变量简写方法

    let x;
    let y;
    let z = 3;

简写方法:

let x, y, z=3;

4.if存在条件简写方法

if (likeJavaScript === true)

简写:

if (likeJavaScript)

只有likeJavaScript是真值时,二者语句才相等

如果判断值不是真值,则可以这样:

    let a;
    if ( a !== true ) {
    // do something...
    }

简写:

    let a;
    if ( !a ) {
    // do something...
    }

5.JavaScript循环简写方法

for (let i = 0; i < allImgs.length; i++)

简写:

for (let index in allImgs)

也可以使用Array.forEach

    function logArrayElements(element, index, array) {
      console.log("a[" + index + "] = " + element);
    }
    [2, 5, 9].forEach(logArrayElements);
    // logs:
    // a[0] = 2
    // a[1] = 5
    // a[2] = 9

    

6.短路评价

给一个变量分配的值是通过判断其值是否为 null 或undefined ,则可以:

    let dbHost;
    if (process.env.DB_HOST) {
      dbHost = process.env.DB_HOST;
    } else {
      dbHost = 'localhost';
    }

简写:

const dbHost = process.env.DB_HOST || ‘localhost’;

7.十进制指数

当需要写数字带有很多零时(如10000000),可以采用指数(1e7)来代替这个数字:for (let i = 0; i < 10000; i++) {}简写:

    for (let i = 0; i < 1e7; i++) {}

    
    // 下面都是返回true
    1e0 === 1;
    1e1 === 10;
    1e2 === 100;
    1e3 === 1000;
    1e4 === 10000;
    1e5 === 100000;

8.对象属性简写

如果属性名与 key 名相同,则可以采用 ES6 的方法:

const obj = { x:x, y:y };

简写:

const obj = { x, y };

9.箭头函数简写

传统函数编写方法很容易让人理解和编写,但是当嵌套在另一个函数中,则这些优势就荡然无存。

    function sayHello(name) {
      console.log('Hello', name);
    }

    
    setTimeout(function() {
      console.log('Loaded')
    }, 2000);

    
    list.forEach(function(item) {
      console.log(item);
    });

    

简写:

    sayHello = name => console.log('Hello', name);

    
    setTimeout(() => console.log('Loaded'), 2000);

    
    list.forEach(item => console.log(item));

    

10.隐式返回值简写

经常使用return语句来返回函数最终结果,一个单独语句的箭头函数能隐式返回其值(函数必须省略{}为了省略return关键字)

为返回多行语句(例如对象字面表达式),则需要使用()包围函数体。

    function calcCircumference(diameter) {
      return Math.PI * diameter
    }

    
    var func = function func() {
      return { foo: 1 };
    };

    

简写:

    calcCircumference = diameter => (
      Math.PI * diameter;
    )

    
    var func = () => ({ foo: 1 });

    

11.默认参数值

为了给函数中参数传递默认值,通常使用 if 语句来编写,但是使用 ES6 定义默认值,则会很简洁:

    function volume(l, w, h) {
      if (w === undefined)
        w = 3;
      if (h === undefined)
        h = 4;
      return l * w * h;
    }

简写:

    volume = (l, w = 3, h = 4 ) => (l * w * h);

    
    volume(2) //output: 24

12.模板字符串

传统的 JavaScript 语言,输出模板通常是这样写的。

    const welcome = 'You have logged in as ' + first + ' ' + last + '.'

    
    const db = 'http://' + host + ':' + port + '/' + database;

ES6 可以使用反引号${}简写:

    const welcome = `You have logged in as ${first} ${last}`;

    
    const db = `http://${host}:${port}/${database}`;

13.解构赋值简写方法

在 web 框架中,经常需要从组件和 API 之间来回传递数组或对象字面形式的数据,然后需要解构它

    const observable = require('mobx/observable');
    const action = require('mobx/action');
    const runInAction = require('mobx/runInAction');

    
    const store = this.props.store;
    const form = this.props.form;
    const loading = this.props.loading;
    const errors = this.props.errors;
    const entity = this.props.entity;

简写:

    import { observable, action, runInAction } from 'mobx';

    
    const { store, form, loading, errors, entity } = this.props;

也可以分配变量名:

    const { store, form, loading, errors, entity:contact } = this.props;
    //最后一个变量名为contact

14.多行字符串简写

需要输出多行字符串,需要使用+来拼接:

    const lorem = 'Lorem ipsum dolor sit amet, consectetur\n\t'
        + 'adipisicing elit, sed do eiusmod tempor incididunt\n\t'
        + 'ut labore et dolore magna aliqua. Ut enim ad minim\n\t'
        + 'veniam, quis nostrud exercitation ullamco laboris\n\t'
        + 'nisi ut aliquip ex ea commodo consequat. Duis aute\n\t'
        + 'irure dolor in reprehenderit in voluptate velit esse.\n\t'

使用反引号,则可以达到简写作用:

    const lorem = `Lorem ipsum dolor sit amet, consectetur
        adipisicing elit, sed do eiusmod tempor incididunt
        ut labore et dolore magna aliqua. Ut enim ad minim
        veniam, quis nostrud exercitation ullamco laboris
        nisi ut aliquip ex ea commodo consequat. Duis aute
        irure dolor in reprehenderit in voluptate velit esse.`

15.扩展运算符简写

扩展运算符有几种用例让 JavaScript 代码更加有效使用,可以用来代替某个数组函数。

    // joining arrays
    const odd = [1, 3, 5];
    const nums = [2 ,4 , 6].concat(odd);

    
    // cloning arrays
    const arr = [1, 2, 3, 4];
    const arr2 = arr.slice()

    

简写:

    // joining arrays
    const odd = [1, 3, 5 ];
    const nums = [2 ,4 , 6, ...odd];
    console.log(nums); // [ 2, 4, 6, 1, 3, 5 ]

    
    // cloning arrays
    const arr = [1, 2, 3, 4];
    const arr2 = [...arr];

    

不像concat()函数,可以使用扩展运算符来在一个数组中任意处插入另一个数组。

    const odd = [1, 3, 5 ];
    const nums = [2, ...odd, 4 , 6];

    

也可以使用扩展运算符解构:

    const { a, b, ...z } = { a: 1, b: 2, c: 3, d: 4 };
    console.log(a) // 1
    console.log(b) // 2
    console.log(z) // { c: 3, d: 4 }

16.强制参数简写

JavaScript 中如果没有向函数参数传递值,则参数为undefined。为了增强参数赋值,可以使用 if 语句来抛出异常,或使用强制参数简写方法。

    function foo(bar) {
      if(bar === undefined) {
        throw new Error('Missing parameter!');
      }
      return bar;
    }

简写:

    mandatory = () => {
      throw new Error('Missing parameter!');
    }

    
    foo = (bar = mandatory()) => {
      return bar;
    }

17.Array.find简写

想从数组中查找某个值,则需要循环。在 ES6 中,find()函数能实现同样效果。

    const pets = [
      { type: 'Dog', name: 'Max'},
      { type: 'Cat', name: 'Karl'},
      { type: 'Dog', name: 'Tommy'},
    ]

    
    function findDog(name) {
      for(let i = 0; i<pets.length; ++i) {
        if(pets[i].type === 'Dog' && pets[i].name === name) {
          return pets[i];
        }
      }
    }

简写:

    pet = pets.find(pet => pet.type ==='Dog' && pet.name === 'Tommy');
    console.log(pet); // { type: 'Dog', name: 'Tommy' }

18.Object[key]简写

考虑一个验证函数

    function validate(values) {
      if(!values.first)
        return false;
      if(!values.last)
        return false;
      return true;
    }

    
    console.log(validate({first:'Bruce',last:'Wayne'})); // true

假设当需要不同域和规则来验证,能否编写一个通用函数在运行时确认?

    // 对象验证规则
    const schema = {
      first: {
        required:true
      },
      last: {
        required:true
      }
    }

    
    // 通用验证函数
    const validate = (schema, values) => {
      for(field in schema) {
        if(schema[field].required) {
          if(!values[field]) {
            return false;
          }
        }
      }
      return true;
    }

    

    
    console.log(validate(schema, {first:'Bruce'})); // false
    console.log(validate(schema, {first:'Bruce',last:'Wayne'})); // true

现在可以有适用于各种情况的验证函数,不需要为了每个而编写自定义验证函数了

19.双重非位运算简写

有一个有效用例用于双重非运算操作符。可以用来代替Math.floor(),其优势在于运行更快。

Math.floor(4.9) === 4 //true

简写:

~~4.9 === 4 //true

以上就是W3Cschool编程狮关于19 个优雅的 JavaScript 简写技巧的相关介绍了,希望对大家有所帮助。

2024 Selenium10个替代品

thbcm阅读(202)

随着自动化测试需求的不断增长,Selenium作为广泛使用的自动化测试工具,虽然功能强大,但也存在一些限制和挑战。在2024年,

C#开源、简单易用的Dapper扩展类库 – Dommel

thbcm阅读(211)

Dapper是一个简单的.NET对象映射器,在速度方面具有”King of Micro ORM”的头衔,几乎与使用原始的ADO.NET数据读取器一样快。ORM是一个对象关系映射器,它负责数据库和编程语言之间的映射。Dapper通过扩展IDbConnection提供一些有用的扩展方法去查询您的数据库。

英伟达又向开源迈了一步「GitHub 热点速览」

thbcm阅读(199)

大家是否还记得 2012 年,Linux 之父 Linus Torvalds 在一次活动中“愤怒”地表达了对英伟达闭源 Linux GPU 驱动的不满?这个场景曾是热门表情包,程序员人手一个。

联系我们