重新认识JavaScript(零)

389 阅读7分钟

前言

这将是一个系列的文章。

正如文章标题所讲,重新认识JavaScript,我将把JavaScript从概念到用法整体梳理一遍,中间或许还会夹杂一些我个人的使用经验以及工具整理。俗话说“温故而知新”,对基础知识的扎实掌握必是为前端大牛不可或缺的条件,共勉之。

什么是JavaScript

JavaScript(JS)是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。最初是作为开发Web页面的脚本语言而闻名的,但是现在也被用到了很多非浏览器环境中,例如Node.js等。JavaScript是一种基于原型 (注1) 编程、多范式 (注2) 的动态脚本语言,并支持面向对象、命令式和声明式(如函数式编程)风格。

JavaScript的标准是ECMAScript。截止2012年,所有的现代浏览器 (注3) 都完整的支持ECMAScript5.1,旧版本的浏览器至少支持ECMAScript3标准。2015年6月17日,ECMA国际组织发布了ECMAScript的第六版,该版本的正式名称为ECMAScript2015,但通常被称为ECMASctipt6或ES6。自此,ECMAScript每年发布一次新标准。

JavaScript在页面上做了什么

浏览器安全

每个浏览器标签页就是其自身用来运行代码的独立容器(这些容器用专业术语称为“运行环境”)。大多数情况下,每个标签页中的代码完全独立运行,而且一个标签页中的代码不能影响另一个标签页(或者另一个网站)中的代码。这是一个好的安全措施,如果不这样,黑客就可以从其他网站盗取信息。

可以用安全的方式在不同网站/标签页中传送代码和数据,这些技术后面再讲。

JavaScript运行次序

当浏览器执行到一段JavaScript代码时,通常会按照从上往下的顺序执行这段代码。这意味着需要注意代码的顺序。例如:

console.log('第一个被执行');

console.log('第二个被执行');

console.log('第三个被执行');

解释代码 vs 编译代码

在解释型语言中,代码自上而下运行,且实时返回运行结果。在代码执行前,不转化为其他形式。

编译型语言在代码运行前需要先转化(或编译)成另外一种形式。比如C/C++先被编译成汇编语言,然后才能由计算机运行。

JavaScript是轻量级解释型语言。

服务器端代码 vs 客户端代码

在web开发中,还有服务器端和客户端代码这两个术语。客户端代码是在用户电脑上运行的代码,在浏览一个网页时,它的客户端代码就会被下载,然后由浏览器运行并展示。

而服务器端代码在服务器上运行,浏览器将结果下载并展示出来。流行的服务器端web语言包括:PHP、Python、Ruby以及JavaScript。JavaScript也可以用作服务器端语言,比如现在流行的Node.js环境。

动态代码 vs 静态代码

“动态”一词既能描述客户端JavaScript,又能描述服务端语言。是指通过按需生成新内容来更新 web页面/应用,使得不同环境下显示不同内容。

没有动态更新内容的网页叫做“静态”页面,所显示的内容不会改变。

怎样向页面添加JavaScript

只需要一个元素<script>,就可以将JavaScript添加到HTML页面中。

内部JavaScript

  1. 首先创建一个HTML页面
  2. </body>标签前插入以下代码:
<script>
    // 在此编写JavaScript代码
</script>

外部JavaScript

  1. 在刚才的HTML页面目录下创建script.js文件
  2. <script>元素替换为:
<script src='script.js'></script>

内联JavaScript处理器

有时候会在HTML中存在着一丝真实的JavaScript代码。或许看起来像下面这样:

<button onclick='sayHello()'>点我</button>

<script>
    function sayHello () {
        console.log('Hello');
    }
</script>

然而请不要这么做,这将使JavaScript污染到HTML,而且效率低下。对于每个需要应用JavaScript的按钮,都得手动添加onclick='sayHello()'属性。

可以用纯JavaScript结构来通过一个指令选取所有按钮。如下:

const buttons = document.querySelectAll('button');

for (let i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', sayHello);
}

这样乍看上去比click属性要长一些,但是这样写会对页面上所有按钮生效,无论多少个,添加或删除,完全无需修改JavaScript代码。

脚本调用策略

要让脚本调用的时机符合预期,需要解决一系列问题。最常见的问题就是:HTML元素是按其在页面中出现的次序调用的,如果用JavaScript来管理页面上的元素,如果JavaScript加载于欲操作的HTML元素之前,则代码将出错。

解决此问题的旧方法:把脚本元素放在文档体的底部(标签之前,与之相邻),这样脚本就可以在HTML解析完毕后加载了。此方案的问题是:只有所有的HTML元素加载完成后才开始脚本的加载/解析过程。对于有大量JavaScript代码的大型网站,可能会带来显著的性能损耗。

async 和 defer

上述的脚本阻塞问题实际有两种解决方案:asyncdefer

先来看asyncdefer的定义。async:规定异步执行脚本(只限外部脚本);defer:规定是否对脚本进行延迟,直到页面加载为止。

浏览器遇到async脚本时不会阻塞页面渲染,而是直接下载后运行。这样脚本的运行次序就无法控制,只是脚本不会阻止剩余页面的显示。当页面中脚本之间彼此独立,且不依赖于本页面的其他任何脚本时,async是最理想的选择。

比如页面要加载以下三个脚本:

<script src='js/verdor/jquery.js' async></script>
<script src='js/script1.js' async></script>
<script src='js/script2.js' async></script>

三者调用顺序是不确定的,jquery可能在script1和script2之前或者之后嗲用,如果这样,这两个脚本中依赖jauery的函数将产生错误。

解决这一问题可用defer属性,脚本将按照页面中出现的顺序加载和运行:

<script src='js/verdor/jquery.js' defer></script>
<script src='js/script1.js' defer></script>
<script src='js/script2.js' defer></script>

脚本调用策略小结:

  • 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用async
  • 如果脚本需要等待解析,且依赖于其他脚本,调用这些脚本时应使用defer,将关联的脚本按所需顺序置于HTML中。

注释

像其他语言一样,JavaScript也可以添加注释。注释只为自己或同事提供代码如何工作的指引。注释非常有用,而且应该经常使用,尤其是在大型项目中。

注释分两类:

  • 在双斜杠后添加单行注释:
// 这是一条注释
  • /**/之间添加多行注释
/*
    我也是
    一条注释
*/

小结

这一篇文章从理论开始,介绍了为什么要使用JavaScript,以及它能做什么事情。下一篇将循序渐进,继续深入JavaScript。

私货时间

当开发一个大型项目时,会经常遇到在外部脚本中按需加载外部脚本的情况。如果每次加载时,都写一遍相似的加载脚本的代码,会增加不少开发与维护的成本。当有这样的需求时,如果直接将这一类功能类似的代码封装成一个脚本,将会成倍的提高开发效率。

像下面这样:

/*
 * @Author: 伊丽莎不白 
 * @Date: 2019-07-23 14:46:44 
 * @Last Modified by: 伊丽莎不白
 * @Last Modified time: 2019-07-23 15:12:24
 */
class ResLoader {
    constructor () {
        this.loaded = false;
    }

    _createElement (type, url) {
        let element = document.createElement(type);
        switch (type) {
            case 'link':
                element.setAttribute('type', 'text/css');
                // 指明被链接文档对于当前文档的关系
                element.setAttribute('rel', 'stylesheet');
                element.setAttribute('href', url);
                break;
            case 'script':
                // 定义script元素包含或src引用的脚本语言
                element.setAttribute('type', 'text/javascript');
                element.setAttribute('src', url);
                break;
        }
        return element;
    }

    _addCallback (target, callback) {
        let thisScope = this;
        target.onload = target.onreadystatechange = function () {
            // readyState属性返回当前文档的状态
            if (!thisScope.loaded && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) {
                thisScope.loaded = true;
                // 防止内存泄漏
                this.onload = this.onreadystatechange = null;
                if (callback &&  typeof callback === 'function') {
                    callback();
                }
            }
        };
    }

    load (url, type, callback) {
        if (type === 'link' || type === 'script') {
            let head = document.getElementsByTagName('head')[0] || document.documentElement;
            let res = this._createElement(type, url);
            if (res) {
                this._addCallback(res, callback);
                head.appendChild(res);
                this.loaded = true;
                return res;
            }
        }
    }
}

注1:基于原型:基于原型的语言具有所谓原型对象的概念。原型对象可以作为一个模版,新对象可以从中获得原始的属性。任何对象都可以制定其自身的属性,既可以是创建时也可以是运行时创建。而且,任何对象都可以作为另一个对象的原型,从而允许后者共享前者的属性。

注2:范式:编程范式是一类典型的编程风格,例如函数式编程、面向对象编程等为不同的编程范式。在面向对象编程中,认为程序是一系列互相作用的对象,而在函数式编程中一个程序会被看作是一个无状态的函数计算的序列。

注3:现代浏览器:现代浏览器是指该浏览器能够理解和支持HTML和XHTML,CSS,ECMAScript及W3C DOM标准。


下一篇:重新认识JavaScript(一)