目录

《你不知道的JavaScript》读书笔记·贰

目录

类型

  • ECMAScript类型细分为语言类型和规范类型
  • 所有的值都有一个对应的语言类型,包括Undefined/Null/Boolean/String/Number/Object/Symbol
  • 类型是值的内部特征,它定义了值的行为,以使其区别于其他值
  • 改变对强制类型转换的成见,看到它的好处并且意识到它的缺点被过分夸大了
  • 七种内置类型,除对象外统称为基本类型
typeof undefined === "undefined"; // 返回类型的字符串值
typeof true === "boolean";
typeof 42 === "number";
typeof "42" === "string";
typeof { life: 42} === "object";
typeof Symbol() === "symbol";

typeof null === "object"; // 由来已久的“bug”
var a = null
(!a && a === "object"); // null是假值且是唯一一个用typeof检测返回“Object”的基本类型值
typeof function a() { /* .. */ } === "function" // function是object的一个“子类型”,是“可调用对象”,有内部属性[[Call]]
typeof [1, 2, 3] === "object" // 数组也是对象的一个”子类型“
  • JavaScript中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。js不做类型强制
  • undefined: 已在作用域中声明但还没有赋值的变量;undeclared: 还没有在作用域中声明过的变量,typeof处理时照样返回"undefined”
  • typeof的安全防范机制(组织报错)来检查undeclared变量
if (typeof DEBUG !== "undefined") {
    console.log("Debugging is starting");
} // 检查全部变量DEBUG是否已被声明,不会出现ReferenceError错误

if (typeof atob === "undefined") {
    atob = function() { /* .. */ }; // 如果在if语句中使用var atob,声明会被提升,即使if条件不成立,去掉var则可以防止有些浏览器特殊的内建全局变量(宿主对象)重复声明的报错
}

// 检查全部变量是否是全局对象的属性,访问不存在的对象属性不会产生ReferenceError错误
if (window.DEBUG) {
    // ..
}

if (!window.atob) {
    // ..
}
  • ployfill: 衬垫代码或者补充代码,用来补充当前运行环境中缺失的功能
  • “稀疏”数组的空白单元的值为undefined
  • 类数组(一组通过数字索引的值)转换为真正的数组
// 工具函数slice(..)
function foo() {
    var arr = Array.prototype.slice.call(arguments);
    arr.push("bam");
    console.log(arr);
}

foo("bar", "baz"); // ["bar", "baz", "bam"]

// ES6内置工具函数Array.from(..)
var arr = Array.from(arguments);
  • 字符串是不可变的,indexOf(..)/charAt(..)
  • 字符串“借用”数组的非变更方法来处理
var a = "foo";
var b = ["f", "o" ,"o"];

var c = Array.prototype.join.call(a, "-");
var d = Array.prototype.map.call(a, function(v) {
    return v.toUpperCase() + ".";
}).join("");

c; // "f-o-o"
d; // "F.O.O."
  • 字符串反转
var c = a.split("").reverse().join(""); // 对包含复杂字符的字符串并不适用,需要能够处理Unicode的工具库
  • JavaScript只有一种数值类型:number(数字),没有真正意义上的整数,“整数“就是没有小数的十进制数
  • 数字类型基于IEEE754标准来实现,该标准通常被称为“浮点数”,JavaScript使用的是“双精度”格式(即64位二进制)
  • 小数点前面的0可以忽略.42,小数点后小数部分最后面的0也可以忽略42.,从代码的可读性考虑,不建议这样写
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"

var b = a * a;
b; // 2.5e+21

var c = 1 / a;
c; // 2e-11
  • tofixed(..)方法指定小数部分的显示位数, 输出字符串形式,不仅适用于数字变量,也适用于数字字面量,下同
  • toPrecision(..)方法用来指定有效数位的显示位数
42.toFixed(3); // SyntaxError

42..toFixed(3); // "42.000"
42 .toFixed(3); // "42.000"
  • 数字字面量还可以用其他格式来表示,如二进制、八进制和十六进制,考虑到代码的易读性,尽量使用小写的0b、0o、0x
0b1110011;
0B1110011;

0o363;
0O363;

oxf3;
oXf3;
  • 二进制浮点数最大的问题0.1 + 0.2 === 0.3; // false
  • 机器精度,误差范围值,对于JavaScript的数字来说,这个值通常是2^-52,从ES6开始,该值定义在Number.EPSILON中,可以使用其来比较两个数字是否相等
// polyfill
if (! Number.EPSILON) {
    Number.EPSILON = Math.pow(2, -52);
}
  • Number.MAX_VALUE: 能够呈现的最大浮点数大约是1.798e+308;Number.MIN_VALUE: 最小浮点数5e-324

  • 整数的安全范围:-2^53-1 ~ 2^53-1 ,超过无法精确呈现,需转换为字符串

  • 是否整数Number.isInterger(..),是否是安全整数Number.isSafeInteger(..)

  • 有些数字操作只适用于32位有符号整数,安全范围就要笑很多,Math.pow(-2, 31) ~ Math.pow(2, 31) - 1

  • a | 0可以将变量a中的数值转换为32位有符号整数,因为数位运算符|只适用于32位整数

  • 特殊数值:不是值的值null和undefined,null是特殊关键字,undefined是内置标识符;不是数字的数字NaN;无穷数infinity/-infinity;零值+0/-0

  • 获取undefined的值通过void表达式,按惯例用void 0,return void setTimeout(doSomething, 100)

  • NaN(not a number)为无效数值/失败数值/坏数值,仍然是数字类型;NaN是一个“警戒值”(sentinel value),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”,如var a = 2 / "foo"; // NaN;NaN是一个特殊值,它和自身不相等,是唯一一个非自反(reflexive, 即x === x不成立)的值,可以使用这个特点判断是否是NaN,也可以使用内建的全局工具函数isNaN(..)来判断一个值是否是NaN,但是其有一个严重的缺陷var b = "foo"; window.isNaN(b); // true,ES6开始可以使用Number.isNaN(..)

  • ES6中新加入了一个工具方法Object.is(..)来判断两个值是否绝对相等,可以用来处理NaN和-0

var a = 2 / "foo";
var b = -3 * 0;

Object.is(a, NaN); // true
Object.is(b, -0); // true

/* polyfill */
if (! Object.is) {
    Object.is = function(v1, v2) {
        // 判断是否是-0
        if (v1 === 0 && v2 === 0) {
            return 1 / v1 === 1 / v2;
        }
        // 判断是否是NaN
        if (v1 ! == v2) {
            return v2 ! == v2;
        }
        // 其他情况
        return v1 === v2
    }
}
  • 在JavaScript中变量不可能成为指向另一个变量的引用(别名),JavaScript引用指向的是值,不能指向别的变量/引用。如果一个值有10个引用,这些引用指向的都是同一个值,它们相互之间没有引用/指向关系

  • 简单值总是通过值复制的方式来赋值/传递,包括null、undefined、字符串、数字、布尔和ES6中的symbol;复合值——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递

  • slice()返回当前数组的一个浅副本(shallow copy);如果要将标量基本类型值传递到函数内并进行更改,就需要将值封装到一个复合值(对象、数组等)中

  • JavaScript为基本数据类型值提供了封装对象,称为原生函数(内建函数)

  • new String(“abc”)创建的是字符串"abc"的封装对象,而非基本类型值"abc”

  • 内部[[Class]]属性:typeof返回值为"object"的对象都包含一个内部属性[[Class]],一般通过Object.prototype.toString(..)来查看

Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call("abc"); // "[object String]" 基本类型值被各自的封装对象自动包装
  • 由于基本类型值没有.length和.toString()这样的属性和方法,JavaScript会自动为基本类型值包装一个封装对象
var a = "abc";

a.length; // 3
a.toUpperCase(); // "ABC" 
  • 一般情况下,我们不需要直接使用封装对象,最好是让引擎自己决定什么时候应该使用封装对象

  • var a = new Boolean(false);该对象是真值(truthy),总是返回true

  • 如果想要自行封装基本类型值,可以使用Object(..)函数(不带new关键字)

  • 如果想要得到封装对象中的基本类型值,可以使用valueOf()函数

var a = new String("abc");
a.valueOf(); // "abc"
  • 构造函数Array(..)不要求必须带new关键字,不带时它会自动补上,如Array(1, 2, 3)

  • Array构造函数只带一个数字参数时,改参数会作为数组的预设长度(length),永远不要创建和使用空单元数组

  • 除非万不得已,否则尽量不要使用Object(..)/Function(..)/RegExp(..)

  • Date(..)和Error(..)的用处大很多,因为没有对应的常量形式来作为他们的替代

  • 调用Date()时不带new关键字,则会得到当前日期的字符串值

  • ES5引入Date.now()获得当前的Unix时间戳,ES5之前可以使用(new Date()).getTime()

  • 创建错误对象主要是为了获得当前运行栈的上下文,包括函数调用栈信息和产生错误的代码行号。错误对象通常与throw一起使用

  • 符号并非对象,是一种简单标量基本类型,主要用于私有或特殊属性,使用Symbol(..)原生构造函数时不能带new关键字

var mysym = Symbol("my own symbol");
mysym; // Symbol("my own symbol");
mysym.toString(); // "Symbol(my own symbol)"
typeof mysyml; // "symbol"

var a = {};
a[mysym] = "foobar";

Object.getOwnPropertySymbols(a); // [ Symbol(my own symbol) ]
  • 原生构造函数有自己的.prototype对象(原生原型),这些对象包含其对应子类型所特有的行为特征

  • 类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)

  • 我们能够从代码中看出哪些地方时显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用

var a = 42;
var b = a + ""; // 隐式强制类型转换
var c = String(a); // 显式强制类型转换
  • 所有安全的Json值(JSON-safe)都可以使用JSON.stringify(..)字符串化,安全的JSON值是指能够呈现为有效JSON格式的值
var a = {
    b: 42,
    c: "42",
    d: [1,2,3]
};

JSON.stringify(a, ["b", "c"], 3);
// "{
//    "b": 42,
//    "c": "42"
// }"
  • 对象会首先被转换为相应的基本类型值,再转换为数字。抽象操作ToPrimitive会首先检查该值是否有valueOf()方法,如果没有就使用toString()的返回值来进行强制类型转换
  • 使用Object.create(null)创建的对象[[Prototype]]属性为null,并且没有valueOf()和toString()方法,因此无法进行强制类型转换
  • 假值(falsy value):可以被强制类型转换为false的值,包括undefined/null/false/+0、-0和NaN/””
  • 封装了假值的对象是真值(truthy value)
  • 真值就是假值列表之外的值
  • +c是+运算符的一元形式,+运算符显式地将c转换为数字,而非数字加法运算
  • 一元运算符+的另一个常见用途是将日期对象强制转为数字,返回Unix时间戳
  • 字位反转,返回2的补码,~x大致等同于-(x+1)
  • 如果indexOf(..)返回-1,~将其转换为假值0,其他情况一律转换为真值
var a = "Hello World"
~a.indexOf("ol"); // 0
!~a.indexOf("ol"); // true
  • 抽象渗漏:在代码中暴露了底层的实现细节,如>=0和==-1

  • -1是一个哨岗位,被赋予了特殊含义的值

  • 使用~~来截除数字值的小数部分,只适用于32位数字

  • 解析字符串中的数字parseInt(..),和转换字符串为数字Number(..)有明显的差别

  • 显示强制类型转换为布尔值常用!!,虽然Boolean(..)是显式的,但并不常用

  • 建议使用Boolean(a)和!!a来进行显式强制类型转换

  • 隐式强制类型转换的作用是减少冗余,让代码更简洁

  • a+"“这样的隐式转换十分常见,一些对隐式强制类型转换持批评态度的人也不能免俗

var a = {
    valueOf: function() { return 42; },
    toString: function() { return 4; }
};

a + ""; // "42"
String(a); // "4"
  • a-0会将a强制类型转换为数字

  • 逻辑运算符||和&&称为“选择器运算符”或者“操作数选择器运算符”更恰当些,因为在JavaScript中它们返回的并不是布尔值,而是两个操作数中的一个

  • ||和&&首先会对第一个操作数执行条件判断,如果其不是布尔值就先进行ToBoolean强制类型转换,然后再执行条件判断

  • 对于||来说,如果条件判断结果为true就返回第一个操作数,如果为false就返回第二个操作数,&&则相反

  • a = a || “hello”(又称为C#的“空值合并运算符”的JavaScript版本)检查变量a,如果还未赋值(或者为假值),就赋予它一个默认值

  • 代码压缩工具常用a&&foo(a),&&运算符也叫作“守护运算符”,前面的表达式为后面的表达式“把关”,如果条件判断未通过,就会悄然终止(“短路”)

  • if(..)语句中的条件判断表达式会发生布尔值隐式强制类型转换

if (!!a && (!!b||!!c)) {
    console.log("yep");
}
  • ES6允许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误,符号不能够被强制类型转为数字,但可以被强制类型转换为布尔值

  • 宽松相等==允许在相等比较中进行强制类型转换,严格相等===不允许,都会检查操作数的类型,区别在于操作数类型不同时它们的处理方式不同

  • 宽松不相等就是宽松相等的相反值

  • ES5规范“抽象相等比较算法”

  • NaN不等于NaN;+0等于-0;两个对象指向同一个值时即视为相等;字符串被强制类型转换为数字;布尔类型被强制类型转换为数字;==中null和undefined相等,它们也与其自身相等

  • “42”是真值还是假值与==本身没有关系,不要使用== true== false

  • 条件判断a==null仅在a为null和undefined时才成立,除此之外其他值都不成立,包括0、false和"“这样的假值,这样的隐式强制类型转换在保证安全性的同时还能提高代码可读性

  • null和undefined不能被封装,Object(null)和Object()均返回一个常规对象

  • “abc" == Object("abc") // 和new String(a)一样Obejct通过ToPrimitive进行强制类型转换,也称为拆封

  • 对一种机制的滥用并不能成为诟病它的接口,我们应该正确合理地运用强制类型转换,避免极端的情况

  • 假值相等的比较

/* 七种假阳性 */
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
  • 非假值的常规情况
42 == "43"; // false
"foo" == 42; // false
"true" == true; // false

42 == "42"; // true
"foo" == [ "foo" ]; // true
  • 极端例子[] == ![] // true0 == "\n"; // true
  • 回过头做一下完整性检查(sanity check),如何安全运用隐式强制类型转换:1. 如果两个的值有true和false,千万不要使用==;2. 如果两边的值中有[]、““或者0,尽量不要使用==
  • typeof总是返回七个字符串之一,其中没有空字符串,所以在类型检查过程中不会发生隐式强制类型转换,是绝对安全的
  • 抽象关系比较:比较双方首先调用ToPrimitive,如果结果出现非字符串,就根据ToNumber规则将双方强制类型转换为数字来进行比较,如果比较双方都是字符串,则按照字母顺序来进行比较
  • 根据规范a <= b被处理为b < a,然后将结果反转,小于等于是“不大于”的意思
  • 为了保证安全,应该对关系比较中的值进行显式强制类型转换
  • 语句(statement)和表达式(expression):语句相当于句子,表达式相当于短语,运算符则相当于标点符号和连接词
  • var a = 3 * 6声明语句,a = 3 * 6赋值表达式,a既是表达式也是语句,通常叫做“表达式语句”
  • 语句都有一个结果值,var的结果值是undefined,代码块(如if语句)的结果值如同一个隐式的返回,即返回最后一个语句的结果值
  • 可以使用eval(..)来获得结果值,切勿在实际开发中这样操作,ES7规范有一项“do表达式”提案,其目的是将语句当做表达式来处理
  • 大部分表达式没有副作用,最常见的有副作用的表达式是函数调用
  • 递增运算符++和递减运算符–都是一元运算符,++在前面时,它的副作用(将a递增)产生在表达式返回结果值之前,++在后面时,副作用则产生在之后,++a++会产生ReferenceError错误,因为运算符需要将产生的副作用赋值给一个变量
  • a++有后续副作用(after side effect)
  • 语句系列逗号运算符将多个独立的表达式语句串联成一个语句b = ( a++, a );
  • delete用来删除对象中的属性和数组中的单元,它通常以一个语句的形式出现,如果操作成功,delete返回true,其副作用是属性被从对象中删除或者单元从array中删除,操作成功是指对于那些不存在或者存在且可配置的属性
  • 赋值运算符a = 42结果值是42,副作用是将42赋值给a,组合赋值运算符,如+=和-=等也是如此
  • 链式赋值(chained assignment),赋值表达式的结果值就能派上用场,如a = b += 2var a, b, c; a = b = c =2
  • var a = b = 42不会对变量b进行声明,在严格模式中这样会产生错误,或者会无意中创建一个全局变量
  • 可以利用赋值语句的副作用将两个if语句合二为一
function vowels(str) {
    var matches;
    
    if (str && (matches = str.match( /[aeiou]/g ))) {
        return matches;
    }
}

vowel("Hello World"); // ["e", "o", "o"]
  • 在JavaScript语法规则中,有时候同样的语法在不同情况下会有不同的解释

  • 大括号{..}可以定义对象常量,也可以是标签语句

  • JavaScript通过标签跳转能够实现goto的部分功能,continue foo是指“执行foo循环的下一轮循环”,break foo是指“跳出标签foo所在循环/代码块,继续执行后面的代码”,并非传统意义上的goto

// 标签为foo的循环
foo: for (var i=0; i<4; i++) {
	for (var j=0; j<4; j++) {
		if ((i * j) >= 3) {
			console.log( "stopping!", i, j );
			break foo;
		}
		console.log(i, j);
	}
}
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// stopping! 1 3
  • 标签也能用于非循环代码块,但只有break才可以

  • 标签不允许使用引号

  • JSON是JavaScript语法的一个子集,但是JSON本身并不是合法的JavaScript语法,JSON-P(将JSON数据封装为函数调用)能将JSON转换为合法的JavaScript语法

  • 代码块结尾不需要分号,会被当作空对象或空代码块

  • 从ES6开始,{..}也可以用于解构赋值(destructing assignment)

  • {..}还可以用作函数命名参数(named function argument)的对象解构(object destructuring),方便隐式地用对象属性赋值

function foo({a, b, c}) {
	console.log(a, b, c);
}

foo({
	c: [1,2,3],
	a: 42,
	b: "foo"
}); // 42 "foo" [1, 2, 3]
  • 在不同上下文中{..}的作用不尽相同,这也是词法和语法的区别所在
  • 事实上JavaScript没有else if,因为if和else只包含单条语句的时候可以省略代码块{},所以else中是一个单独的if语句
  • 运算符优先级(operator precedence):用,来连接一系列语句的时候,它的优先级最低;&&运算符的优先级高于=;&&运算符先于||执行;||的优先级又高于? :
var a = 42;
var b = "foo";
var c = false;

var d = a && b || c ? c || b ? a : c && b : a;
d; // 42
  • 对&&和||来说,如果从左边的操作数能够得出结果,就可以忽略右边的操作数,我们将这种现象称为“短路”(即执行最短路径)
  • opts && opts.cool中opts条件判断如同一道安全保护,因为如果opts未赋值,表达式opts.cool会出错;opts.cache || primeCache(),首先判断opts.cache是否存在,如果是则无需调用primeCache()函数,这样可以避免执行不必要的代码
  • 另一种说法是&&和||比? :的绑定更强
  • 多个相同优先级的运算符同时出现,就涉及组合(隐式),从技术角度来说&&运算符是左关联(||也是),所以a && b && c会被处理为(a && b) && c
  • 右关联不是指从右向左执行,而是指从右往左组合,任何时候执行顺序都应该是从左到右
  • ?:是右关联,a ? b : c ? d : e的组合顺序是a ? b : (c ? d : e),另一个右关联(组合)的例子是=运算符
  • 有个折中之策,在编写程序时既要依赖运算符优先级/关联规则,也要适当使用()来自行控制运算符的组合和执行顺序
  • 有时JavaScript会自动为代码补上缺失的分号,即自动分号插入(Automatic Semicolon Insertion, ASI),如果解析器发现代码行可能因为缺失分号而导致错误,那么它就会自动补上分号
  • 语句代码块结尾不用带;,所以不需要用到ASI
  • 其他涉及ASI的情况是break、continue、return和yield(ES6)等关键字
  • 是否应该完全依赖ASI来编码,这是JavaScript社区中最具争议性的话题之一(除此之外还有Tab和空格之争)
  • ASI实际上是一个“纠错”(error correction)机制,ASI的目的在于提高解析器的容错性。解析器报错就意味着代码有问题,在代码中省略那些“不必要的分号”就意味着“这些代码解析器无法解析,但是仍然可以运行”
  • 是遵循语法规则来编码,还是打规则的擦边球?依赖于ASI实际上是将换行符当作有意义的“空格”来对待,在一些语言(如Python)中空格是有意义的,但切勿认为ASI真的会将换行符当作有意义的字符
  • 在所有需要的地方加上分号,将对ASI的依赖降到最低
  • JavaScript不仅有各种类型的运行时错误(TypeError、ReferenceError、SyntaxError等),它的语法中也定义了一些编译时错误
  • 在编译早期发现的代码错误叫做“早期错误”(early error),这些错误在代码执行之前是无法用try..catch来捕获的
  • 非法的正则表达式也会产生早期错误
  • ES6中的TDZ(Temporal Dead Zone,暂时性死区)指的是由于代码中的变量还没有初始化而不能被引用的情况
{
    typeof a; // undefined
    typeof b; // ReferenceError!(TDZ)
    
    let b;
}
  • 对ES6中的参数默认值而言,参数被省略或被赋值为undefined效果都一样,都是取改参数的默认值
  • 向函数传递参数时,arguments数组中的对应单元会和命名参数建立关联以得到相同的值,相反,不传递参数就不会建立关联,但是,在严格模式种并没有建立关联一说。它是JavaScript语言引擎底层实现的一个抽象泄露(leaky abstraction),并不是语言本身的特性
function foo(a) {
	a = 42;
	console.log( arguments[0] );
}

foo(2); // 42(linked)
foo(); // undefined(not linked)
  • arguments数组已经被废止,不过它并非一无是处,只需遵守一个原则,即不要同时访问命名参数和其对应的arguments数组单元
  • finally中的return会覆盖try和catch中的return的返回值
  • switch..case中可以出现各种表达式,它会将表达式的结果值和true进行比较,true和true之间是严格相等比较,如果case表达式的结果为真,但不是严格意义上的true,则条件不成立
/*a与case表达式逐一进行比较,直到break或者switch代码块结束*/
switch(a) {
	case 2:
		break;
	case 42:
		break;
	default:
		// 执行缺省代码
}
  • default是可选的,首先遍历并找到所有匹配的case,如果没有匹配则执行default中的代码,如果其中没有break,则继续执行直到break或者switch代码块结束

  • 由宿主环境(浏览器等)创建并提供给JavaScript引擎的变量——所谓的“宿主对象”(包括内建对象和函数)

  • 由于浏览器演进的历史遗留问题,在创建带有id属性的DOM元素时也会创建同名的全局变量

  • 一个广为人知的JavaScript的最佳实践是:不要扩展原生原型

  • 如果向Array.prototype中加入新的方法和属性,假设它们确实有用,设计和命名都很得当,那它最后很有可能会被加入到JavaScript规范当中

  • 支持新规范的新版本浏览器会完全替代老版本浏览器,而非在老版本上做扩展

  • polyfill(或者shim)能有效地为不符合最新规范的老版本浏览器填补填补缺失的功能,让你能够通过可靠的代码来支持你想要支持的运行环境

  • JavaScript社区存在这样的争论,即是否可以对一个功能做不完整的polyfill

  • 绝大部分网站/Web应用程序的代码都存放在多个文件中,这些文件和内联代码的运行方式更像是相互独立的JavaScript程序,但是并非总是如此

  • 这些文件中的代码在共享的命名空间中运行,并相互交互,但全局变量作用域的提升机制在这些边界中不适用

  • 如果文件中的代码发生错误,它会像独立的JavaScript程序那样停止,但是后续的script中的代码(仍然共享global)依然会接着运行,不会受影响

  • 可以使用代码来动态创建script,将其加入到页面的DOM中,但在内联代码中不可以出现字符串,一旦出现即被视为代码块结束,常用的变通方法是"</sc>" + "<ript>";

  • script标签的一个已废止的用法是在内联代码中包含HTML或XHTML格式的注释,<! –和–>(HTML格式的注释)在JavaScript中被定义为合法的单行注释分隔符,这是老的技术导致的,切勿再使用它们

  • 保留字不能用作变量名,分为四类:关键字(function、switch)、预留关键字(enum、class、extend)、null常量和true/false布尔常量

  • 在ES5之前,保留字也不能用来作为对象常量中的属性名称或者键值,但是现在已经没有这个限制

  • JavaScript规范对于函数中的参数的个数,以及字符串常量的长度等并没有限制,但是由于JavaScript引擎实现各异,规范在某些地方有一些限制,不同的JavaScript引擎的限制各异

  • 在处理这些情况的时候需要格外小心,要编写健壮的代码,并且写好文档