在HTML中使用JavaScript
<script>元素
script
标签具有以下常用属性
- async: 表示立即下载脚本,但不立即执行,不阻塞页面渲染,仅对外部脚本有效,但是执行顺序按加载完成先后顺序,所以一个页面最好只包含一个延迟脚本在允许的时候执行,
- defer:立即下载文件,并在DOM渲染之后执行,执行顺序按指定顺序执行
- src:外部文件地址
带有 src 属性的 script 标签之间不应该再有额外的 JavaScript 代码,因为在执行的时候会被忽略。
为了避免 JavaScript 阻塞页面的渲染,一般把 JavaScript 标签放在 body 标签内容的后面。
最好使用外部文件来代替嵌入的代码段。
<noscript> 标签
为了浏览器能够在不支持 JavaScript 时平稳退化,创造了 noscript 标签。只有在以下情况会显示出来:
- 浏览器不支持脚本
- 浏览器支持脚本,但脚本被禁用
基本概念
语法
JavaScript 语言每句结尾的 ;
不是必须的,但是最好在实际中加上分号。
变量
只定义未初始化的变量默认值为 undefined
let
ES6 新增了 let
命令来声明变量。用法类似于 var
,但是所声明的变量只在 let
命令所在的代码块内生效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
for
中的 i
循环就特别适合使用 let
来声明,针对 for 循环,设置循环变量的那部分是父级作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i);
}
// abc
以上使用 let
声明变量的代码输出了 3 次 abc
。这表明函数内部的变量 i
与循环变量 i
不在同一个作用域,有各自独立的作用域。而使用 var
声明变量则只输出一次 abc
,表名使用 var
声明的变量作用域是全局作用域(同时也是函数作用域),不是块级作用域。
另外,使用 var
变量还存在“变量提升”的现象,即函数的声明都会提升到代码的头部进行,这样在变量真正的声明位置之前就可以使用变量,只不过其值为未赋值时默认的 undefined
,而 let
声明的变量就必须在其声明之后才能使用。
const
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const
作用域与 let
相同。
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
const foo = {
};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {
}; // TypeError: "foo" is read-only
上面代码中,foo
所存储的是一个地址,这个地址指向一个对象,这个地址不可改变,但是对象本身是可以改变的。
暂时性死区(temporal dead zone,简称 TDZ)
ES6 明确规定,如果区块中存在 let
和 const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
object类型
在 ECMAScript 中,object 类型是所有它的实例的基础,object 所具有的所有属性和方法也同一样存在于更具体的对象中。
object 的每个实例都具有下列属性和方法:
- constructor:保存着用于创建当前对象的函数
- hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中作为参数的属性名以字符串形式指定,例
o.hasOwnProperty("name")
- isPrototypeOf(object):用于检查传入的对象是否是当前对象的原型
- propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用
for-in
语句来枚举。参数的属性名必须以字符串形式指定。 - toLocalString():返回对象的字符串表示,该字符串与执行环境的地区对应。
- toString():返回队象的字符串表示。
- valueOf():返回对象的字符串、数值或布尔值表示。通常与
toString()
方法的返回值相同。 - Object.getOwnPropertySymbols(): 获取指定对象的所有Symbol属性名
- Reflect.ownKeys:返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
操作符
一元操作符
++
、--
操作符遵循以下规则:
- 应用于一个包含有效数字字符的字符串时,先将其转换为数字值,然后在执行加减 1 的操作,字符串变量变成数值变量。
- 应用于一个不包含有效数字的字符串时,将变量的值置为
NaN
,字符串变量变成数值变量 - 应用于布尔值时,先转换为 0 或 1 之后再进行加减 1 的操作,布尔变量变为数值变量
- 在应用于对象时,先调用对象的
valueOf()
方法以取得一个可供操作的值,然后对其根据前面的规则进行处理。如果结果为 NaN ,则在调用toString()
方法后再应用上述规则,对象变量变成数值变量。
var o = {
valueOf: function() {
return -1;
}
}
o--; //-2
位操作符
只有无符号右移 >>>
,没有无符号左移
函数
JavaScript 函数没有重载。如果定义了两个名字相同的函数,则名字只属于后者。
变量、作用域和内存问题
基本类型和引用类型的值
基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
基本类型:Undefined
, Null
, Boolean
, Number
, String
, symbol
(ES2015新增)
对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。
symbol
阮一峰
Symbol 值作为对象属性名时,不能用点运算符。Symbol 值作为对象属性名时,不能用点运算符。
复制变量值
- 基本类型的复制是直接把目标对象复制一个新值赋给新的变量。
- 引用类型的复制也会将目标对象复制一份放到新的变量空间,但是不同的是,这个值是个指针,指针指向存储的堆中的一个对象。复制结束后两个变量将引用同一个对象,改变其中一个变量也会影响另一个变量。如果想要进行深拷贝,一般需要遍历所有属性逐个复制。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "jack";
alert(obj2.name); //"jack"
传递参数
ECMAScript 中的所有函数的参数都是按值传递的,也就意味着,把函数外部的值赋值给函数内部的值时,就如同把值从一个变量复制到另一个变量。
function setName(obj) {
obj.name = "jack";
}
var person = new Object();
setName(person);
alert(person.name); //"jack"
一定要特别注意在局部作用域内对全局引用类型值的修改,因为这将直接影响全局对象的值。
检测变量类型
使用 typeof
来检查变量的类型。当变量的值是一个对象或者是 Null
时, typeof
会返回 “object”。
为了能够进一步指导引用类型的值是哪种对象,我们需要使用 instanceof
操作符。
person instanceof Object;
colors instanceof Array;
pattern instanceof RegExp;
所有引用类型都是
object
的实例,所以instanceof
操作符对于object
都会返回true
执行环境及作用域
每个函数都有其执行环境。代码在一个环境中执行,会创建变量对象的一个作用域链。作用域链的用途是保证 对 执行环境有权访问的 所有变量和函数 的有序访问。作用链的前端始终都是当前执行的代码所在环境的变量对象。
内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
延长作用域链
有些语句可以在作用域的前端临时增加一个变量对象。该变量对象会在代码执行后移除。
可以延长作用域链的语句:
- try-catch 语句的 catch 块
会将制定的对象添加到作用域链中 - with 语句
会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
由于延长作用域链改变了原始的作用域链,会对 JavaScript 的性能造成影响,所以在看中性能的页面中应该少用上述语句。
垃圾收集
JavaScript 具有自动垃圾回收机制。
引用类型
object 类型
访问对象属性有两种方法:
- 点表示法:如同其他面向对象语言中一样
- 方括号表示法:要访问的属性以字符串的形式放在方括号中,有点事可以通过变量来访问属性
var propertyName = "name";
alert(person[propertyName]); //"jack"
Array 类型
ECMAScript 数组与其他语言数字最大的区别是数组中的每一项都可以保存任何类型的数据,并且可以自动调整大小。
对数组进行赋值,如果索引超过了数组长度,则数组会自动增加到该索引加 1
的长度。
var num = [1, 2, 3];
num[4] = 5;
num[3]; //undefined
num.length; //5
数组
length
属性不是只读的,可以对其进行修改,从而达到从末尾移除或增加新项的目的。
数组最多包含4 294 967 295 = 2^32
个项。
转换方法
数组调用 valueOf()
方法将返回数组本身,调用 toString()
方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。实际上是调用了数组每一项的 toString()
方法。
如果使用 join()
方法,可以返回不同分隔符的字符串。
var num = [1, 2, 3];
num.valueOf(); //[1, 2, 3]
num.toString(); // "1,2,3"
num.join("|").toString(); //"1|2|3"
如果数组中某一项的值是
null
或者是undefined
,则在调用转换方法之后用空字符串表示。
栈方法
- push()
- pop()
队列方法
- push()
- shift():移除数组中第一项并返回该项,同时数组长度减 1
反向队列
- unshift():在数组前端添加任意个项,并返回新数组的长度
- pop()
重排序方法
- reverse():反转数组项的顺序
- sort():会调用每个数组项的
toString
方法,然后比较得到的字符串,默认按升序排列。还可以接受一个比较函数作为参数。
即使数组中的每一项都是数值,
sort()
方法也会先调用toString()
方法,所以数值数组调用sort()
方法不一定能够得到正确的结果。
操作方法
- concat()
var num = [1, 2, 3];
var num2 = num.concat(4, [5, 6]);
num2;//[1, 2, 3, 4, 5, 6]
- slice(): 不会影响原数组
var num = [1, 2, 3, 4, 5, 6]
//接收一个参数,返回参数指定位置到数组结尾的所有项
num.slice(1); //[2, 3, 4, 5, 6]
//接收两个参数,返回起始和结束位置之间的项 [start, end)
num.slice(1, 4)