JavaScript 入门指南

  • JavaScript 高级程序设计(第3版),Nicholas C.Zakas 著,人民邮电出版社出版
  • JavaScript DOM 编程艺术,Jeremy Keith 著,人民邮电出版社出版于 2007 月 01 月

本篇主体内容摘自读上述书时做的笔记,加入了一些平时看技术文章存下的内容,整合在一起。适合有一定 JavaScript 基础的人阅读。

JavaScript 简史

JavaScript 起源

JavaScript 1.0 出现在 1995 年推出的 Netscape Navigator 2 浏览器中。JavaScript 与 Java 无关,最初叫 LiveScript,只是 Java 太火,才贴脸改名。

IE3 加入 JScript 且 Win95 捆绑 IE 后,网景日子不好过,于是和 Sun 公司联合 ECMA(欧洲计算机制造商协会)对 JavaScript 语言进行标准化,其结果就是 ECMAScript 语言。

JavaScript 是一种脚本语言,一般运行于浏览器(NodeJS 封装了 Chrome 的 JavaScript 解析引擎 V8,使得后端也有 JavaScript 一席之地)。

浏览器之争

Netscape Navigator 4 发布于 1997 年 6 月,IE4 发布于同年 10 月。这两种浏览器都加大了对 DOM 的支持,因此可以通过 JavaScript 完成的功能大大增加,于是也诞生一个新词:DHTML。

DynamicHTML 指 HTML、CSS 和 JavaScript 三种技术结合。

  • 利用 HTML 把网页标记为各种元素
  • 利用 CSS 设计各有关元素的排版样式并确定它们在窗口中的显示位置
  • 利用 JavaScript 实时地操控和改变各有关样式

故事先放一边,讲讲这三种技术。

三位一体的网页

结构层 - HTML

由标记语言组成,标签对网页内容的语义含义做出了描述。

表示层 - CSS

CSS对如何显示的问题做出回答。

元素的nodeName值为字符串,style值为对象,当然元素也是对象。

element.style.fontFamily,表示待取元素的样式,CSS样式中(font-family)的-要使用 Camel 记号法(fontFamily)。DOM style 属性不能检索 <head> 部分和外部 CSS 文件里声明的样式,只能检索元素内写的style,所以并没有什么卵用。

通过className检索给定元素的class属性值:element.className,为元素追加类:element.className += ' anotherClass'

1
2
3
4
5
6
7
8
9
10
11
// 为元素追加类的函数
function addClass(element, value) {
if (!element.className) {
element.className = value;
} else {
newClassName = element.className;
newClassName += ' ';
newClassName += value;
element.className = newClassName;
}
}

所以动态改变元素样式,还是用上例中的className方法。

overflow属性告诉浏览器是否需要显示滚动条,有四个值:

  • visible 不裁剪溢出的内容,浏览器将溢出的内容呈现在容器的显示区域以外的地方,全部内容在浏览器中均可见
  • hidden 裁剪溢出的内容,只有部分内容可见
  • scroll 裁剪,但通过滚动条可以查看隐藏的内容
  • auto 类似scroll,只有在发生内容溢出时才显示滚动条,内容没溢出不显示滚动条。

行为层 - JavaScript

JavaScript = ECMAScript + DOM + BOM,负责内容应该如何对事件做出反应。其中 DOM & BOM 是宿主(浏览器)对象,所以在 JavaScript 中的 Object 类型,在浏览器下无效。

JavaScript 之 ECMAScript

程序设计语言分为解释型和编译型两大类。

Java 和 C++ 等需要编译器(Compiler),编译器是一种能把高级语言写出来的源代码翻译为二进制可执行文件的程序。运行速度快,可移植性也更好,但学习成本高。

解释型语言不需要编译器,而是需要解释器,如上文提到的 Chrome 内置的 JavaScript V8 解析引擎。

编译型语言的错误代码在编译阶段就会被发现并报告,而解释型只有等到实际执行到有关代码才能发现错误。

JavaScript 中变量区分大小写,对数据类型不做强制声明,被称为弱类型(Weakly typed),因此可以随便改变变量的数据类型。

JavaScript 之 DOM
节点 - Node
  • Document: window.document
  • DocumentType: <!DOCTYPE html>
  • DocumentFragment: 文档片段
  • 元素节点(nodeType: 1)
  • 属性节点(nodeType: 2):class='active'
  • 文本节点(nodeType: 3)

Text 节点是Element/Attribute 节点的文本内容。

获取元素有多种方法,如:var el = document.getElementById('x');,表示获取id值为x的元素,再如通过标签名获取文档的第一个段落元素:var p = document.getElementsByTagName('p')[0];

获取元素属性的两种方式:

  • DOM Core:el.getAttribute('y');
  • HTML-DOM:el.y;

获取元素节点的文本:p.firstChild.nodeValue;,表示获取段落里的内容,因为内容是文本节点,它的父元素就是获取到的p啊。

当要获取元素样式的数值时,使用parseInt(el.style.x),这样属性的单位没有了,拿到的只有数值。

tips:innerHTML属性不是 W3C DOM 的标准(最开始由 IE 实现,后来各大浏览均支持)。

节点名(nodeName) 节点类型(nodeType)
大写的标签名 1
属性名 2
文本 3
#document 9
DocumentType.name 10
#document.fragment 11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
document.doctype //<!DOCTYPE html>
document.domain === location.hostname //true, x.x.com
document.documentElement //页面文档
document.referrer //从哪个网页跳转过来的
document.characterSet //UTF-8
document.readyState //loading加载HTML -> interactive加载外部资源 -> complete
document.links //文档所有的链接,数组
document.images //所有图像,数组
document.contentType //text/html
document.styleSheets //文档所有的模式。var allStyleSheets = [].slice.call(document.styleSheets);
document.cookie
document.hasFocus(); //文档是否获得了焦点
document.getElementsByTagName('p')[0].attributes; //节点属性

var p = document.getElementsByTagName('p')[0];
p.id = 'pid';
p.className = 'pcls';
p.attributes; //NamedNodeMap { 0: id, 1: class }
var pc = [].slice.call(p.attributes);
pc[0].nodeName; //id
pc[0].nodeType; //2
pc[0].nodeValue; //pid
pc[0].textContent; //pid
createElement() & createTextNode() & appendChild()

通过原生 JavaScript 创建元素节点:

1
2
3
4
5
6
7
8
var p = document.createElement('p');
var text = new Text('hello'); // 等效于var text = document.createTextNode('hello');
p.appendChild(text);
document.body.appendChild(p);
p.id = 'pId';
p.setAttribute('class', 'pClass');
text.appendData('haha'); // hellohaha,给文本追加内容
text.remove(); // 移除文本节点
insertBefore()

该方法由 W3C 规定,属原生。形式如:parentElement.insertBefore(newElement, targetElement);。表示在目标节点(targetElement)前面插入新节点(newElement),它们的父节点是parentElement

1
2
3
4
5
6
7
8
9
// 编写insertAfter()方法
function insertAfter(newElement, targetElement) {
var parent = targetElement.parentNode;
if (parent.lastChild === targetElement) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement, targetElement.nextSibling);
}
}
JavaScript 之 BOM

BOM(Browser Object Model)提供的接口:

  • navigator
  • location
  • screen
  • window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
navigator.userAgent === navigator.appCodeName + '/' + navigator.appVersion; // true,最新版的 chrome/firefox/EDGE 的 appCodeName 都是 Mozilla
navigator.language; // 'zh-CN'
navigator.onLine; // 是否联网

location === document.location //true
location.hostname; // 等效于document.domain
location.href;
location.search;

screen.availHeight; // 1040。我显示分辨率的高是 1080,可见它的值是显示器分辨率高减去任务栏占用的高度
screen.availWidth; // 1920
screen.colorDepth; // 24

var arr = [];
for ( var prop in window ) {
arr.push(prop);
}
arr.length; // 204。表示 window 对象有 204 个属性或方法啊,仅 window.document 提供的方法就有很多

上面四个对象中,window是 BOM 的核心对象,即浏览器的一个实例,是 JavaScript 访问浏览器窗口的接口,也是 ECMAScript 规定的Global对象。直接在浏览器的console里输入window,即可看到由浏览器提供的诸多属性和方法。PS:全局变量不可通过delete删除。

制定标准

就在网景和微软为了压倒对方而以 DOM 为武器展开营销大战时,W3C 推出标准化的 DOM。因此 1998 年 10 月各大浏览器为了世界的和平联合 W3C 共同完全了 DOM Level 1。

W3C 对 DOM 的定义是:“一个与系统平台和编程语言无关的接口,程序和脚本可以通过这个接口动态地对文档的内容、结构和样式进行访问和修改。”

微软在 IE5 内建了对标准化 DOM 的支持,但同时继续支持其专有的 Microsoft DOM。

网景采取一种坚决果断的做法,发布一种与之前无任何共同点的 Netscape Navigator 6,该浏览器跳过了主版本号并使用一个与之前完全不同的渲染引擎。当然 NN6 也支持标准 DOM,但不再向后支持早期的 Netscape DOM。

之后微软的浏览器开发在推出 IE6 后停顿了下来,除了坚定的支持 DOM Level 1 之外,凄惨。正因为 IE6 与 Windows XP 太棒,导致多年来只更新补丁不换代,因此国内市场前端工程师对 XP 与 IE6 深恶痛绝。

这时其它浏览器开始露面,Apple 在 2003 年发布支持标准 DOM 的 Safari。Opera 和 Konquerer 浏览器也良好支持 DOM 标准。

故事讲完了,开始干货。

JavaScript 特性

JavaScript 是 Web 浏览器的语言,属函数式编程。它建立在一些非常好的想法和少数坏的想法上:

好的方面

  • 函数
  • 弱类型
  • 动态对象
  • 富有表现力的对象字面量表示法,即 JSON

坏的方面

  • 全局变量的编程模型

JavaScript 的函数是基于词法作用域(Lexical scoping)的顶级对象,是第一个成为主流lambda的语言。相对 Java 而言,JavaScript 与 Lisp 和 Scheme 有更多共同点,它是披着 C 外衣的 Lisp。

现今主流编程语言都使用强类型,因为强类型允许编译器在编译时检测错误。而 JavaScript 是一门弱类型语言。

原型继承是 JavaScript 中一个有争议的特性。JavaScript 有一个无类别(class-free)对象系统,在这个系统中,对象直接从其他对象继承属性。

JavaScript 依赖于全局变量来进行连接。所有编译单元的所有顶级变量被整合到一个全局对象(Global Object,在浏览器平台中叫做window)的公共命名空间中。

JSLint 是 JavaScript 解析器,能分析 JavaScript 问题并报告它包含的缺点。

ECMAScript 是 JavaScript 的核心,它的宿主环境除了浏览器,还有 Adobe 的 Flash 和后端大红大紫的 NodeJS。它提出诸如语法、类型、关键字、操作符及对象等约定,供编程语言(这里特指 JavaScript)实现。

浏览器中 JavaScript 语言的对象:

  • 用户定义对象
  • 内建对象(native object):如ArrayMathDate
  • 宿主对象(host object):浏览器提供的对象,如window

window对象对应浏览器窗口本身,这个对象的属性和方法通常称为 BOM,它提供window.openwindow.blur等方法。

IE5.5-7 内核基于 ECMAScript 3.x 开发,现在 ECMAScript 已经发布第 6 版,第 7 版也在草稿里。另外第 4 版因太激进,未正式发布,目前浏览器主要使用的还是 ECMAScript 5,ES6 的普遍应用应该在 2016 年甚至 2017 年以后。

运算符

1
2
'113' - '5'; // 108,除加号之外,其它运算符的操作数自动转换为数值
'113' * '5'; // 565

表单

<form>元素对应HTMLFormElement类型。

获取表单:var f1 = document.forms['name']

提交表单:给通用的<input>或自定义的<button>添加属性type='submit'。表单控件获得焦点时,按回车键即可提交(textarea除外),或者f1.submit()

获取表单中的元素:f1.elements['name']。若为radio,则返回的是NodeList

1
2
3
4
5
6
7
8
9
10
11
12
13
//元素获取焦点
EventUtil.addHandler(window, 'load', function(event) {
document.forms['name'].elements['name'].focus();
});
// 若是HTML5中,则直接加入autofocus属性即可获得焦点
<input type='text' autofocus>

//选中文本
f1.elements['inputText'].select();
//取得选择的文本内容
function getSelectedText(content) {
return content.value.substring(content.seletionStart, content.selectionEnd);
}

获取页面所有复选框选中元素,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// index.html
<input type="checkbox" value="a" checked="checked"/>
<input type="checkbox" value="b"/>
<input type="text" value="abc"/>
<input type="password" value="rui"/>
<input type="checkbox" value="f" checked="checked"/>

// script
var domList = document.getElementsByTagName('input');
var checkboxs = [];
var l = domList.length;
// 以下使用 while 的方式比 for 循环的效率要高,若 domList 使用次数较多,应该将其保存在局部变量中供调用
while (l--) {
if (domList[l].type === 'checkbox') {
checkboxs.push(domList[l]);
}
}
console.log(checkboxs[0].value); // a

变量提升

匿名函数的执行环境具有全局性,所以this === window

客户端检测

  • 能力检测。如document.getElementById在 IE5 之前版本不支持,尽量使用typeof检测
  • 怪癖检测(Quirks detection):检测 BUG
  • 用户代理检测:每次 HTTP 请求过程中,navigator.userAgent作为响应头部发送

JSON

JSON对象的序列化方法:JSON.stringify(obj, ['name', 'anotherName'], 4); //只序列化所列属性,缩进4个空格

1
2
3
4
// 将 JSON 数据解析成 JavaScript 对象
var data = '{"name": "turui"}';
var obj = JSON.parse(data);
log(obj.name); // turui

高级技巧

所有函数都是对象。

检查新对象的this是否指向该对象:if ( this instanceof Object ) {...}

bind:将函数绑定到指定环境的函数。

1
2
3
4
5
function bind(fn, context) {
return function() {
return fn.apply(context, arguments);
};
}

Tips

JavaScript 垃圾收集方法

  • 标记清除
  • 引用计数(JavaScript 引擎主流收集方式)

浏览器兼容

定义一个工具类,用于跨浏览器:

1
2
3
4
5
6
7
8
9
10
11
12
var eventUtil = {
// element, type & handler 分别对应 DOM 2 级中的 x, click, function() { ... }
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
}
}

事件对象:在触发 DOM 上事件时都会产生一个对象。target属性,用于获取事件目标,即该事件属于哪个元素。停止事件冒泡:event.stopPropagation();,另外preventDefault()是阻止事件默认行为。

HTTP用于电脑间通信,无状态,虽然使用 TCP 协议。

跨域:<script src="http://www.x.com/jsonp.js></script>,该方式用于 GET 方式,不支持POST方式。

HTML 提供的 XHR2,IE10 以下不支持,在服务器端写:

1
2
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET');

监听滚动条

1
2
3
window.onscroll = function() {
var top = document.documentElement ? document.documentElement.scrollTop : document.body.scrollTop;
}

观察器

1
2
3
4
5
6
7
8
var MutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
var observer = new MutationObserver(callback);
observer.observe(element, options);
var callback = function(records) {
records.map(function(record) {
log('MutationType: ' + record.type);
});
});

观察对象属性变化:

1
2
3
4
5
6
7
8
var o = {};
Object.observe(o, function(changes) {
changes.forEach(function(change) {
log(change.type, change.name, change.oldValue);
});
});
o.foo = 1; //add foo undefined
o.foo = 2; //update foo 1

定义表

是把一些缩略词语及其解释生成为一个清单的最佳选择,由定义标题和定义描述组成。


TR

Tu Rui

uRuier

这是什么?

tips:

  • 变量、函数区分大小写
  • 标识符是变量、函数或属性等的名字,正则表达式为:^[a-zA-Z_$][a-zA-Z_$0-9]+
  • ECMAScript 中字符串不可变,重新赋值只会新建一个字符串;对象是一组数据与功能的集合
  • 可以在switch语句的case选择项中使用任意数据类型,也可以用变量。
  • 后定义的变量、函数覆盖先定义的变量、函数。
  • 引用类型的值是保存在内存中的对象,JS不允许直接访问内存中的位置。
  • 所有函数的参数都是按值传递的,修改了参数,也不影响原值。
  • 执行环境(EC)定义了变量、函数有权访问的其他数据,每个 EC 都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中,最外围的 EC 是全局执行环境。在 Browser 中全局执行环境是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。每个函数都有自己的 EC,当执行流进行一个函数时,函数的环境就进入环境栈中。作用域的前端始终都是当前代码所在环境的变量对象。