JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。
数据类型
JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值)
- 原始类型:数值(number):整数和小数(比如1和3.14)
- 原始类型:字符串(string):文本(比如Hello World)。
- 原始类型:布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
- 合成类型:对象(object):各种值组成的集合
- undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
- null:表示空值,即此处的值为空。
只是声明变量而没有赋值,则该变量的值是undefined。undefined是一个特殊的值,表示“无定义”。
变量提升
:JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)
函数
函数是处理数据的方法,但是JavaScript 语言将函数看作一种值, 把它当成一种数据类型,可以赋值给变量,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。
function命令声明的代码区块,就是一个函数
function f1() {}
f1.name // "f1"
var f2 = function () {};
f2.name // "f2"
var f3 = function myName() {};
f3.name // 'myName'
作用域
JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
ES6 又新增了块级作用域,
- 全局变量(global variable) 函数外部声明的变量,它可以在函数内部读取。
- 局部变量”(local variable) 在函数内部定义的变量,外部无法读取, 会在该作用域内覆盖同名全局变量
闭包
闭包(closure) 是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
函数内部声明的变量,函数外是无法读取, 只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数
闭包的最大用处有两个,
- 可以读取函数内部的变量,封装对象的私有属性和私有方法
- 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
function f1() {
var n = 999;
function f2() {
console.log("函数f2就是闭包,可以读取变量n:" + n );
}
return f2;
}
立即调用的函数表达式(IIFE)
圆括号()是一种运算符,跟在函数名之后,表示调用该函数
但是想在定义函数之后立即调用该函数。在函数的定义之后加上圆括号会报错,原因是会出现歧义:function这个关键字即可以当作语句,也可以当作表达式
而JavaScript 引擎规定,如果function关键字出现在行首,一律解释成语句。因此,JavaScript 引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。
解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面
// 语句
function f() {}
// 表达式
var f = function f() {}
//错误写法
function(){ /* code */ }();// SyntaxError: Unexpected token (
//IIFE正确写法
(function(){ /* code */ }());
(function(){ /* code */ })();
//避免污染全局变量
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:
- 一是不必为函数命名,避免了污染全局变量;
- 二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
对象(object)
对象是 JavaScript 语言的核心概念,也是最重要的数据类型。
对象就是一组“键值对”(key-value)的集合,
对象的每一个键名又称为“属性”(property),
如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。
var obj = {
//obj对象的属性foo
'foo': 'Hello',
//obj对象的方法p
p: function (x) {
return 2 * x;
}
};
对象采用大括号表示, JavaScript 引擎会产生歧义: 是对象还是代码块
为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面只能是表达式
{ foo: 123 } // 代码块 标签foo指向表达式123
({ foo: 123 }) // 对象 一个包含foo属性的对象
严格模式
早期的 JavaScript 语言有很多设计不合理的地方,但是为了兼容以前的代码,又不能改变老的语法,只能不断添加新的语法,引导程序员使用新语法。
严格模式是从 ES5 进入标准的,主要目的有以下几个。
- 明确禁止一些不合理、不严谨的语法,减少 JavaScript 语言的一些怪异行为。
- 增加更多报错的场合,消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 为未来新版本的JavaScript 语法做好铺垫。
//老版本的引擎会把它当作一行普通字符串,加以忽略。新版本的引擎就会进入严格模式。
'use strict';
//严格模式可以用于整个脚本,也可以只用於单个函数。
- 显式报错
- 只读属性不可写
设置字符串的length属性,会报错 - 只有取值器(getter)、没有存值器(setter)的属性赋值,会报错
- 禁止扩展的对象不可扩展
- eval、arguments 不可用作标识名
- 函数不能有重名的参数
- 禁止八进制的前缀0表示法
- 只读属性不可写
- 增强的安全措施
- 全局变量显式声明
- 禁止 this 关键字指向全局对象
- 函数内部不得使用fn.caller、fn.arguments,否则会报错。这意味着不能在函数内部得到调用栈了
- 函数内部使用arguments.callee、arguments.caller将会报错。
- 禁止删除变量
- 静态绑定
某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,必须在编译阶段就确定。- 禁止使用 with 语句
- 创设 eval 作用域
- arguments 不再追踪参数的变化
- 向下一个版本的 JavaScript 过渡
- 非函数代码块不得声明函数
- 严格模式新增了一些保留字(implements、interface、let、package、private、protected、public、static、yield等)