整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

最近很火的Vue Vine是如何实现一个文件中写多个组件

在今年的Vue Conf 2024大会上,沈青川大佬(维护Vue/Vite 中文文档)在会上介绍了他的新项目Vue Vine。Vue Vine提供了全新Vue组件书写方式,主要的卖点是可以在一个文件里面写多个vue组件。相信你最近应该看到了不少介绍Vue Vine的文章,这篇文章我们另辟蹊径来讲讲Vue Vine是如何实现在一个文件里面写多个vue组件。

看个demo

我们先来看普通的vue组件,about.vue代码如下:

<template>
  <h3>i am about page</h3>
</template>

<script lang="ts" setup></script>

我们在浏览器中来看看编译后的js代码,代码如下:

const _sfc_main = {};

function _sfc_render(_ctx, _cache) {
  return _openBlock(), _createElementBlock("h3", null, "i am about page");
}

_sfc_main.render = _sfc_render;
export default _sfc_main;

从上面的代码可以看到普通的vue组件编译后生成的js文件会export default导出一个_sfc_main组件对象,并且这个组件对象上面有个大名鼎鼎的render函数。父组件只需要import导入子组件里面export default导出的_sfc_main组件对象就可以啦。

搞清楚普通的vue组件编译后是什么样的,我们接着来看一个Vue Vine的demo,Vue Vine的组件必须以.vine.ts 结尾,home.vine.ts代码如下:

async function ChildComp() {
  return vine`
    <h3>我是子组件</h3>
  `;
}

export async function Home() {
  return vine`
  <h3>我是父组件</h3>
    <ChildComp  />
  `;
}

如果你熟悉react,你会发现Vine 组件函数和react比较相似,不同的是return的时候需要在其返回值上显式使用 vine 标记的模板字符串。

在浏览器中来看看home.vine.ts编译后的代码,代码如下:

export const ChildComp = (() => {
  const __vine = _defineComponent({
    name: "ChildComp",
    setup(__props, { expose: __expose }) {
      // ...省略
    },
  });

  function __sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
    return _openBlock(), _createElementBlock("h3", null, "我是子组件");
  }

  __vine.render = __sfc_render;
  return __vine;
})();

export const Home = (() => {
  const __vine = _defineComponent({
    name: "Home",
    setup(__props, { expose: __expose }) {
      // ...省略
    },
  });

  function __sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
    return (
      _openBlock(),
      _createElementBlock(
        _Fragment,
        null,
        [_hoisted_1, _createVNode($setup["ChildComp"])],
        64,
      )
    );
  }

  __vine.render = __sfc_render;
  return __vine;
})();

从上面的代码可以看到组件ChildCompHome编译后是一个立即调用函数,在函数中return了__vine组件对象,并且这个组件对象上面也有render函数。想必细心的你已经发现了在同一个文件里面定义的多个组件经过编译后,从常规的export default导出一个默认的vue组件对象变成了export导出多个具名的vue组件对象。

接下来我们将通过debug的方式带你搞清楚Vue Vine是如何实现一个文件内导出多个vue组件对象。

createVinePlugin函数

我们遇见的第一个问题是需要找到从哪里开始着手debug?

来看一下官方文档是接入vue vine的,如下图:

从上图中可以看到vine是一个vite插件,以插件的形式起作用的。

现在我们找到了一切起源就是这个VineVitePlugin函数,所以我们需要给vite.config.ts文件中的VineVitePlugin函数打个断点。如下图:

接下来我们需要启动一个debug终端。这里以vscode举例,打开终端然后点击终端中的+号旁边的下拉箭头,在下拉中点击Javascript Debug Terminal就可以启动一个debug终端。

在debug终端执行yarn dev,在浏览器中打开对应的页面,比如:http://localhost:3333/ 。此时代码将会停留在我们打的断点VineVitePlugin函数调用处,让代码走进VineVitePlugin函数,发现这个函数实际定义的名字叫createVinePlugin,在我们这个场景中简化后的createVinePlugin函数代码如下:

function createVinePlugin() {
  return {
    name: "vue-vine-plugin",
    async resolveId(id) {
      // ...省略
    },
    async load(id) {
      // ...省略
    },
    async transform(code, id) {
      const { fileId, query } = parseQuery(id);
      if (!fileId.endsWith(".vine.ts") || query.type === QUERY_TYPE_STYLE) {
        return;
      }
      return runCompileScript(code, id);
    },
    async handleHotUpdate(ctx) {
      // ...省略
    }
  };
}

从上面的代码可以看到插件中有不少钩子函数,vite会在对应的时候调用这些插件的钩子函数,比如当vite解析每个模块时就会调用transform等函数。

transform钩子函数的接收的第一个参数为code,是当前文件的code代码字符串。第二个参数为id,是当前文件路径,这个路径可能带有query。

transform钩子函数中先调用parseQuery函数根据当前文件路径拿到去除query的文件路径,以及query对象。

!fileId.endsWith(".vine.ts") 的意思是判断当前文件是不是.vine.ts结尾的文件,如果不是则不进行任何处理,这也就是为什么文档中会写Vue Vine只支持.vine.ts结尾的文件。

query.type === QUERY_TYPE_STYLE的意思是判断当前文件是不是css文件,因为同一个vue文件会被处理两次,第一次处理时只会处理template和script这两个模块,第二次再去单独处理style模块。

transform钩子函数的最后就是调用runCompileScript(code, id)函数,并且将其执行结果进行返回。

runCompileScript函数

接着将断点走进runCompileScript函数,在我们这个场景中简化后的runCompileScript函数代码如下:

const runCompileScript = (code, fileId) => {
  const vineFileCtx = compileVineTypeScriptFile(
    code,
    fileId,
    compilerHooks,
    fileCtxMap,
  );

  return {
    code: vineFileCtx.fileMagicCode.toString(),
  };
};

从上面的代码可以看到首先会以code(当前文件的code代码字符串)为参数去执行compileVineTypeScriptFile函数,这个函数会返回一个vineFileCtx上下文对象。这个上下文对象的fileMagicCode.toString(),就是前面我们在浏览器中看到的最终编译好的js代码。

compileVineTypeScriptFile函数

接着将断点走进compileVineTypeScriptFile函数,在我们这个场景中简化后的compileVineTypeScriptFile函数代码如下:

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  const vineFileCtx: VineFileCtx = createVineFileCtx(
    code,
    fileId,
    fileCtxCache,
  );
  const vineCompFnDecls = findVineCompFnDecls(vineFileCtx.root);
  doAnalyzeVine(compilerHooks, vineFileCtx, vineCompFnDecls);
  transformFile(
    vineFileCtx,
    compilerHooks,
    compilerOptions?.inlineTemplate ?? true,
  );

  return vineFileCtx;
}

在执行compileVineTypeScriptFile函数之前,我们在debug终端来看看接收的第一个参数code,如下图:

从上图中可以看到第一个参数code就是我们写的home.vine.ts文件中的源代码。

createVineFileCtx函数

接下来看第一个函数调用createVineFileCtx,这个函数返回一个vineFileCtx上下文对象。将断点走进createVineFileCtx函数,在我们这个场景中简化后的createVineFileCtx函数代码如下:

import MagicString from 'magic-string'

function createVineFileCtx(code: string, fileId: string) {
  const root = babelParse(code);
  const vineFileCtx: VineFileCtx = {
    root,
    fileMagicCode: new MagicString(code),
    vineCompFns: [],
    // ...省略
  };
  return vineFileCtx;
}

由于Vue Vine中的组件和react相似是组件函数,组件函数中当然全部都是js代码。既然是js代码那么就可以使用babel的parser函数将组件函数的js代码编译成AST抽象语法树,所以第一步就是使用code去调用babel的parser函数生成AST抽象语法树,然后赋值给root变量。

我们在debug终端来看看得到的AST抽象语法树是什么样的,如下图:

从上图中可以看到在body数组中有两项,分别对应的就是ChildComp组件函数和Home组件函数。

接下来就是return返回一个vineFileCtx上下文对象,对象上面的几个属性我们需要讲一下。

  • root:由.vine.ts文件转换后的AST抽象语法树。
  • vineCompFns:数组中存了文件中定义的多个vue组件,初始化时为空数组。
  • fileMagicCode:是一个由magic-string库new的一个对象,对象中存了在编译时生成的js代码字符串。magic-string是由svelte的作者写的一个库,用于处理字符串的JavaScript库。它可以让你在字符串中进行插入、删除、替换等操作,在编译时就是利用这个库生成编译后的js代码。toString方法返回经过处理后的字符串,前面的runCompileScript函数中就是最终调用vineFileCtx.fileMagicCode.toString()方法返回经过编译阶段处理得到的js代码。

findVineCompFnDecls函数

我们接着来看compileVineTypeScriptFile函数中的第二个函数调用findVineCompFnDecls

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  // ...省略
  const vineCompFnDecls = findVineCompFnDecls(vineFileCtx.root);
  // ...省略
}

通过前一步我们拿到了一个vineFileCtx上下文对象,vineFileCtx.root中存的是编译后的AST抽象语法树。

所以这一步就是调用findVineCompFnDecls函数从AST抽象语法树中提取出在.vine.ts文件中定义的多个vue组件对象对应的Node节点。我们在debug终端来看看组件对象对应的Node节点组成的数组vineCompFnDecls,如下图:

从上图中可以看到数组由两个Node节点组成,分别对应的是ChildComp组件函数和Home组件函数。

doAnalyzeVine函数

我们接着来看compileVineTypeScriptFile函数中的第三个函数调用doAnalyzeVine

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  // ...省略
  doAnalyzeVine(compilerHooks, vineFileCtx, vineCompFnDecls);
  // ...省略
}

经过上一步的处理我们拿到了两个组件对象的Node节点,并且将这两个Node节点存到了vineCompFnDecls数组中。

由于组件对象的Node节点是一个标准的AST抽象语法树的Node节点,并不能清晰的描述一个vue组件对象。所以接下来就是调用doAnalyzeVine函数遍历组件对象的Node节点,将其转换为能够清晰的描述一个vue组件的对象,将这些vue组件对象组成数组塞到vineFileCtx上下文对象的vineCompFns属性上。

我们在debug终端来看看经过doAnalyzeVine函数处理后生成的vineFileCtx.vineCompFns属性是什么样的,如下图:

从上图中可以看到vineCompFns属性中存的组件对象已经能够清晰的描述一个vue组件,上面有一些我们熟悉的属性propsslots等。

transformFile函数

我们接着来看compileVineTypeScriptFile函数中的第四个函数调用transformFile

function compileVineTypeScriptFile(
  code: string,
  fileId: string,
  compilerHooks: VineCompilerHooks,
  fileCtxCache?: VineFileCtx,
) {
  // ...省略
  transformFile(
    vineFileCtx,
    compilerHooks,
    compilerOptions?.inlineTemplate ?? true,
  );
  // ...省略
}

经过上一步的处理后在vineFileCtx上下文对象的vineCompFns属性数组中已经存了一系列能够清晰描述vue组件的对象。

在前面我们讲过了vineFileCtx.vineCompFns数组中存的对象能够清晰的描述一个vue组件,但是对象中并没有我们期望的render函数、setup函数等。

所以接下来就需要调用transformFile函数,遍历上一步拿到的vineFileCtx.vineCompFns数组,将所有的vue组件转换成对应的立即调用函数。在每个立即调用函数中都会return一个__vine组件对象,并且这个__vine组件对象上都有一个render属性。

学习过 JavaScript 的可能会了解,JavaScript 的宿主浏览器只有一个线程运行 JavaScript,除了 JavaScript 的线程,浏览器中单个页面还有一些其他线程,例如:UI 线程负责处理渲染 DOM 元素;GUI 线程用于处理与用户交互的逻辑;网络线程用于发送接收 HTTP 请求;file 线程用于读取文件;定时器线程处理定时任务等等。

单线程原因

为什么不能像很多高级语言一样支持多线程呢?假定 JavaScript 同时有两个线程,一个线程在HTML中创建了一个标签元素,另一个线程删除了这个标签,这时浏览器应该执行什么操作?浏览器中 JavaScript 的主要用途是操作 DOM 。这决定了它只能是单线程,否则会带来很复杂的同步问题。为了避免复杂性,大部分主流浏览器的 JavaScript 运行环境只支持单线程。

JavaScript 的事件驱动

既然 JavaScript 只支持单线程,那么有人可能会好奇为什么浏览器中的 JavaScript 可以同时发送多个网络请求或者执行多个事件回调函数呢?

这是因为 JavaScript 是基于事件驱动,当需要进行网络请求时,JavaScript 线程会把请求发送给 network 线程执行,并等待执行结果;当进行文件读取时则调用 file 线程,然后等待结果。然后 JavaScript 会一直轮询事件库 event loop,直到有事件完成,这时浏览器会驱动 JavaScript 去执行事件的回调函数。这就是 JavaScript 的事件驱动模型。

web worker诞生

单线程的最大问题是不能利用多核 CPU 的优点,HTML5 推出的 Web Worker 标准,允许 JavaScript 创建多线程,但是子线程受主线程约束,且不得操作 DOM 。所以,这个新标准不会产生多线程同步的问题。

适用场景

Web Worker 能解决传统的 JavaScript 单线程出现的执行阻塞问题,因而适合以下几种业务场景:

  • 并行计算;
  • ajax 轮询;
  • 耗时的函数执行;
  • 数据预处理/加载。

函数介绍

1、创建

初始化一个 Web Worker,由于不是所有的浏览器都支持 Web Worker,所以需要判断一下浏览器是否支持:

if (window.Worker) {//判断浏览器是否支持web worker
    var worker = new Worker('test.js');//创建一个线程,参数为需要执行的JavaScript文件
}

2 、向线程传递参数

新的线程的上下文环境跟原宿主环境相对独立的,所以变量作用域不同,如果需要互相读取变量的话需要通过消息发送的方式传输变量,例如:

worker.postMessage('test'); //数据类型可以是字符串
worker.postMessage({method: 'echo', args: ['Work']});//数据类型可以是对象

3 、主线程接受消息

跟上述场景类似,主线程也需要通过监听的方式获取辅线程的消息:

worker.onmessage = function (event) {
  console.log('接收到消息: ' + event.data);
}

4 、线程加载脚本

子线程内部也可以通过函数加载其他脚本:

importScripts('script1.js','script2.js');

5 、关闭线程

// 主线程中关闭子线程
worker.terminate();
// 子线程关闭自身
self.close();

使用 JavaScript 多线程实现非阻塞全排列

1 、什么是全排列

从 n 个不同元素中任取 m(m≤n)个元素,按照一定的顺序排列起来,叫做从 n 个不同元素中取出 m 个元素的一个排列。当 m=n 时所有的排列情况叫全排列。

2 、为什么使用多线程处理

这里并非突出使用 JavaScript 实现全排列的优势,而是在实际项目中类似这种科学运算相关的算法可能会消耗一定的 CPU,由于 JavaScript 是解释型语言,运算性能是它的弱项,而且浏览器中运行的 JavaScript 又是单线程的,所以一旦出现性能问题可能会导致线程阻塞,阻塞之后会导致页面卡顿,非常影响用户体验。使用 webworker 的多线程功能将这个运算函数单独 fork 出一个子线程去运行,运行完成之后发送结果给主线程,可以有效避免性能问题。

3 、代码示例

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; 
charset=UTF-8">
<title>JavaScript实现全排列</title>
<script type="text/JavaScript">
function combine() {//点击按钮向webworker线程发送请求  
    var worker = new Worker('http://wiki-code.oss-cn-beijing.aliyuncs.com/html5/js/worker.js');
    worker.postMessage(document.getElementById("str").value);
        worker.onmessage= function (event) {                        
	        document.getElementById("result").innerHTML  =   event.data ; //监听JavaScript线程的结果
	    };
}
</script>
</head>
<body>
    <input type="text" id="str" />
    <button onclick="combine()">全排列</button>
    结果是:<div id="result" style="width:500px;height:500px;word-break: break-all;"></div>
</body>
</html>

worker.js 代码如下:

function getGroup(data, index = 0, group = []) {//生成全排列
    var need_apply = new Array();
    need_apply.push(data[index]);
    for(var i = 0; i < group.length; i++) {
        need_apply.push(group[i] + data[index]);
    }
    group.push.apply(group, need_apply);
    if(index + 1 >= data.length) return group;
    else return getGroup(data, index + 1, group);
}
onmessage = function(message){//监听主线程的数据请求
    var msg = message.data;
    if(msg == "") postMessage("请输入正确的字符串");
    else {
        var data = msg.split("");//将字符串转数组
        postMessage(getGroup(data));
    }
}

上述代码实现了一个使用 JavaScript 的 Web Worker 实现的全排列的功能。上半部分是主线程的代码,主要实现了创建子线程、发送数据给子线程、接收子线程的消息这几个功能;下半部分是子线程,子线程主要负责运算,并将运算结果发送给主线程。

总结

早期的 JavaScript 由于考虑操作 DOM 的一致性问题,以及当时的网页没有过多的交互所以不需要大量的计算,所以只支持单线程。这在多核 CPU 时代的劣势愈发明显,所以 HTML5 中推出多线程解决这个问题。回顾本章主要介绍了 Web Worker 的使用方式以及其适用场景。

更多详细内容:https://developer.mozilla.org/zh-CN/docs/Web/API/Worker

章有代码部分,如果有阅读困难,请到原文阅读:https://blog.jing.do/4212

前言:本篇文章是我在查询时偶然间发现的,虽然年代久远但是依旧非常适合入门学习,特此翻译下分享给大家,顺便给大家加了一些备注方便阅读(特意加粗刷存在感)。原文全文和链接在最后

———————-以下是正文———————-

备注:本篇文章是34岁的程序员Ayman Hourieh在2006年发布的。我找到了他网站的链接,但是网站已经不存在了。我用Wayback Machine找到了文章的镜像。为了让更多人看到把他摘录分享出来。我只做了一些链接的修改。

JavaScript是一个功能强大的面向对象语言。表面上看他他和JAVA和C非常相似,但是他却截然不同,其核心在于JavaScript更像一个功能性语言。本篇文章是一些JavaScript的小提示,一部分提供一些类C语言的功能模拟,另一部分是想要提高性能并探讨下脚本语言中一些比较难懂的东西。索引如下:

多用途的Array

队列

二叉树

String Concatenation 对比 Array.join

给对象绑定方法

使用自定义排序

Assertion

静态局部变量

null, undefined, and delete

深层嵌套

使用Firebug

$ 和 $$

console.log(format, obj1, …)

console.trace()

inspect(obj)

多用途的Array

尽管JavaScript在数据结构方面看起来很奇特,他的Array比其他编程语言(如C ++或Java)用途更加广泛。 它通常用作数组或关联数组,后面将演示如何将其用作堆栈,队列和二叉树。 复用Array而不是编写其他的数据结构有两个好处:首先,不用浪费时间去重写已经存在的功能,其次,内置浏览器对JavaScript的运行将更高效。

大家都知道栈是后进先出:最后插入的会被最先移除。array有两个方法来实现栈的功能。他们是push()和pop()。push()用于插入item到array的尾部,而pop()则用于移除并返回最后一个item。以下是代码的实例:

varstack=[];stack.push(2);// 当前栈是 [2]stack.push(5);// 当前栈是 [2, 5]vari=stack.pop();// 当前栈是 [2]alert(i);// 显示 5

队列

队列是先进先出的:现行插入的将会被最先移除。array可以用push()和shift()来实现队列。push()用于插入item到尾部,shift()用于移除第一个item。案例如下:

varqueue=[];queue.push(2);// 现在的队列 [2]queue.push(5);// 现在的队列 [2, 5]vari=queue.shift();// 现在的队列 [5]alert(i);// 显示 2

值得注意的是,array还有一个unshift()的函数。这个函数用于将item放到array的头部。所以栈也可以使用unshift()/shift(),而队列可以用unshift()/pop()。

如果这些函数名称让你迷茫了(译者注:因为unshift()是处理头部,所以相对应的栈需要从头部出去,队列需要换到尾部),你可以给他们取个别名,比如,创建队列的方法名为add和remove:

varqueue=[];queue.add=queue.push;queue.remove=queue.shift;queue.add(1);vari=queue.remove();alert(i);

二叉树

二叉树是在树的节点表示数据。每个节点有一个数据并且有两个子节点(左叉树和右叉树)。在C语言里,这种数据结构通常使用指针来实现。这种实现方法也可以在JavaScript中使用对象和引用来实现。然而,对于一些小的二叉树,有一种更简单便捷的方法,array的第一个item作为树的头。 如果可以使用以下公式计算节点i,则索引左右节点:

leftChild(i)=2i+1rightChild(i)=2i+2

这张图揭示了这个算法: (来自于Wikipedia):

正如你所看到的,这种方法并不是JavaScript的独有之处,但是在处理小的二叉树时非常有用。 例如,您可以编写自己的函数来获取和设置节点的值或子节点,并遍历二叉树,这些方法与做计算或for循环一样简单。但是,这种方法的缺点是随着树的深度增加,浪费的存储空间越来越多。

String Concatenation 对比 Array.join

大家都知道,如果做太多的字符串链接会让性能下降(译者注:不知道的去补课),并且这个非常容易避免。如果你想要用各个字符来组成一个字符串,最差的方法是使用+把所有的字符结合到一起:

str='';for(/* each piece */){str+=piece;// bad for performance!}returnstr;

这种方法将使用太多的字符串链接,会让性能枯竭。

在JavaScript中有个更好的办法,就是Array.join(),他可以让所有array内的元素连接成一个字符串:

vartmp=[];for(/* each piece */){tmp.push(piece);}str=tmp.join(' ');// 用空格作为分隔符returnstr;

该方法不会受到额外的字符串对象的影响,通常执行的非常高效

给对象绑定方法

任何使用JavaScript事件的人都可能遇到了一种情况,他们需要将对象的方法分配给事件处理程序。 这里的问题是事件处理程序会被HTML调用,即使它们最初被绑定到另一个对象。 为了解决这个问题,我用一个函数将对象和方法绑定; 它他会运行对象和方法,并返回一个在该对象调用该方法的函数。 我在Prototype中找到了一个窍门,并且写了以下函数来在不包含Prototype的项目中使用它:

functionbind(obj,method){returnfunction(){returnmethod.apply(obj,arguments);}}

如何使用:

varobj={msg:'Name is',buildMessage:function(name){returnthis.msg+' '+name;}}alert(obj.buildMessage('John'));// displays: Name is Johnf=obj.buildMessage;alert(f('Smith'));// displays: undefined Smithg=bind(obj,obj.buildMessage);alert(g('Smith'));// displays: Name is Smith

使用自定义排序

排序是一个常见的工作。 JavaScript提供了一种排序数组的方法。 但是,该方法默认按字母顺序排列 —— 非字符串元素在排序之前被强制转换为字符串,这个使得数字排序会有意想不到的结果:

varlist=[5,10,2,1];list.sort()// list is now: [1, 10, 2, 5]

这个解释很容易: 数字被强制转换成了字符串,所以10编程了’10’而2变成了’2’,那么JavaScript对比两个字符串的时候,先对比第一个字符。如果str1的第一个字符出现在字符集中的第一个字符之前,则str1被认为是“小于”str2。 在我们的情况下,’1’在’2’之前,所以’10’小于’2’。

幸运的是,JavaScript提供一个重写机制,让我们可以自定义如何排序,我们用a和b两个元素最为例子:

如果a

如果a=b,返回0

如果a>b,返回大于0

写这样的程序比较容易:

functioncmp(a,b){returna-b;}

我们现在可以使用这个方法来做排序:

varlist=[5,10,2,1];list.sort(cmp);//listisnow:[1,2,5,10]

Array.sort()牛逼的地方是允许更复杂的排序。 假设你有一个论坛帖子,每个帖子看起来像:

varpost={id:1,author:'...',title:'...',body:'...'}

如果你想用id排序,只需要创建以下函数:

functionpostCmp(a,b){returna.id-b.id;}

可以说,使用浏览器的方法进行排序将比在JavaScript中实现排序函数更有效。 但是,数据应该在服务器端进行排序。所以,除非必要,否则不应该让数据在客户端排序。

Assertion

Assertion是一种常用的调试技术,它用于确保表达式在执行期间计算为真。 如果表达式计算为假,则表示代码中可能出现的错误。 JavaScript缺少一个内置的Assertion功能,但幸运的是,它很容易编写一个。 如果传递的表达式的计算结果为假,以下实现会抛出AssertException类型的异常:

functionAssertException(message){this.message=message;}AssertException.prototype.toString=function(){return'AssertException: '+this.message;}functionassert(exp,message){if(!exp){thrownewAssertException(message);}}

自己抛出异常并不是非常有用,但是当与有用的错误消息或调试工具结合使用时,您可以检测到有问题的部分。

您还可以使用以下代码段检查异常是否为Assertion异常:

try{// ...}catch(e){if(einstanceofAssertException){// ...}}

该函数的使用类似于C或Java:

assert(obj!=null,'Object is null');

如果obj碰巧为null,Firefox将在JavaScript控制台中打印以下消息:

uncaughtexception:AssertException:Objectisnull

Static Local Variables

大家知道,一些语言像C ++,他们有静态变量的概念,用于函数调用。 JavaScript并不支持此技术。 然而,“功能也是对象”让这个功能成为可能。 方法是:将静态变量变为函数的属性。 假设我们要创建一个计数器函数:

functioncount(){if(typeofcount.i=='undefined'){count.i=0;}returncount.i++;}

当第一次调用count时,count.i是未定义的,所以if条件为true,count.i为0。因为我们将变量存储为一个函数属性,它将在函数调用之间保留它的值, 因此它可以被认为是一个静态变量。

这里有个性能提升小技巧:

functioncount(){returncount.i++;}count.i=0;

虽然第一个例子将Count的所有逻辑封装在主体中,但第二个例子更为有效。

null, undefined, and delete

因为JavaScript有undefined和null所以他不同于其他语言。null是一个特别的数值,他表示没有数值。null会被认为一个特别的对象,因为typeof null会返回null。

undefined表示变量没有被定义,或者定义了但没有给数值。以下情况都会显示undefined:

// i is not declared anywhere in codealert(typeofi);vari;alert(typeofi);

虽然undefined和null是两个不同的类型,但是如果使用 == ,会被判定为相等,但是如果是 === 则不等。

JavaScript还有一个删除操作符,”undefines”一个属性,可以将其应用于对象属性和数组成员,使用var声明的变量不能被删除,而是隐式声明(implicitly declared )的变量可以:

varobj={val:'Some string'}alert(obj.val);// displays 'Some string'deleteobj.val;alert(obj.val);// displays 'undefined'

深层嵌套

如果您需要在深层嵌套对象上执行多个操作,最好将其引用到临时变量中,而不是每次对其进行解引用。 例如,假设您想在文本字段上执行一系列操作:

document.forms[0].elements[0]

建议您存储到变量中,并使用此变量而不是以上构造:

varfield=document.forms[0].elements[0];// Use field in loop

每个点都导致一个操作来检索一个属性,在一个循环中,这些操作是相加的,所以最好做一次,将对象存储在变量中并重新使用它。

Using Firebug

Firefox有一个非常棒的扩展,用于调试名为Firebug的JavaScript代码。 它提供一个具有断点和堆栈视图的调试器,以及一个JavaScript控制台。 它还可以监视Ajax请求。 此外,扩展提供了一组JavaScript函数和对象来简化调试。 您可以在Firebug的文档页面中详细研究它们。 这里有一些我觉得有用的:

$ and $$

熟悉Prototype的人马上就认出他们了,

$() 接受一个字符串参数,并返回其ID是传递的字符串的DOM元素。(译者注:Jquery

$('nav')// returns the element whose id is #nav.

$$()返回DOM的数组

$$('div li.menu')// returns an array of li elements that are// located inside a div and has the .menu class

console.log(format, obj1, …)

console对象提供显示log消息的方法,这个将比alert更加好用,console.log()有点像C里面的printf,他会将输入转化为字符串在console中展示:

vari=1;console.log('i value is %d',i);// prints:// i value is 3

console.trace()

此方法打印一个堆栈跟踪调用它。 它不需要输入参数

inspect(obj)

该功能需要一个参数。 它切换到检查选项卡并检查传递的对象。