《JavaScript语言入门教程》记录整理:入门和数据类型

本系列基于阮一峰老师的《JavaScrip语言入门教程》或《JavaScript教程》记录整理,教程采用知识共享 署名-相同方式共享 3.0协议。这几乎是学习js最好的教程之一(去掉之一都不过分)

最好的教程而阮一峰老师又采用开源方式共享出来,之所以重新记录一遍,一是强迫自己重新认真读一遍学一遍;二是对其中知识点有个自己的记录,加深自己的理解;三是感谢这么好的教程,希望更多人阅读了解

入门篇

js介绍

  1. JavaScript 是一种轻量级的脚本语言和嵌入式(embedded)语言,其只能通过宿主环境(host,浏览器或node环境)提供I/O操作
  2. 语法角度,JS是一种"对象模型"语言。支持函数式编程、"面向对象"编程、过程式编程等
  3. js核心语法包括:基本的语法构造(比如操作符、控制结构、语句)和标准库(就是一系列具有各种功能的对象比如ArrayDateMath等)。然后就是宿主提供的API(比如浏览器提供的BOM、DOM和Web互联网相关的类;Node环境提供文件操作API、网络通信API等)
  4. JavaScript 的所有值都是对象
  5. js可以采用事件驱动(event-driven)和非阻塞式(non-blocking)设计,实现高并发、多任务处理

历史

  1. 1995年,Brendan Eich 只用了10天,完成了js的第一版,其设计借鉴了C、java、Scheme、Awk、Self、Perl、Python等多种语言,同时也留下了各种设计缺陷(导致常常需要学习各种解决问题的模式)
  2. JavaScript与Java是两种不同的语言,两者的关系仅仅是js的基本语法和对象体系最开始是想要模仿Java,而后又与当时Java的公司Sun有合作,也借助Java的声势,从而后来改名叫JavaScript
  3. ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。ECMAScript 只用来标准化 JavaScript 的基本语法结构。但其他标准如 DOM 的标准就是由 W3C组织(World Wide Web Consortium)制定的
  4. 2007年,ECMAScript 4.0版草案发布,但是过于激进,导致后面中止了 ECMAScript 4.0 ,将其中一小部分功能发布为ECMAScript 3.1。之后又将其改名为 ECMAScript 5。
  5. 2015年6月,ECMAScript 6 正式发布,且更名为“ECMAScript 2015”。之后每年发布一个ECMAScript版本
  6. 周边大事记

基本语法

  1. js执行单位是行(line),一行一行地执行,一般,一行就是一个语句
  2. 语句(statement)执行某种操作、表达式(expression)用于得到返回值。凡是 JavaScript 语言中预期为值的地方,都可以使用表达式(这一点使js某些使用很灵活)
  3. 语句以分号结尾,一个分号表示一个语句结束。多个语句可以写在一行内。分号前无内容,表示空语句。表达式不需要分号结尾
  4. 变量是对“值”的具名引用,即为"值"取名。变量的名字就是变量名。如下使用var声明一个变量a,并赋值1
var a=1;
  1. 只声明变量而不赋值,则该变量的值是undefined,表示"无定义"。同时变量赋值时不写var也有效,但不建议。变量必须先声明再使用,否则会报错not defined
  2. 一条var命令可以声明多个变量。js是动态类型语言,变量类型可以随时更改。
var a = 1;
a = 'hello';
  1. 使用var重新声明一个已存在的变量,是无效的,重新声明时赋值,相当于重新赋值
  2. 变量提升:js的执行方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行,这样就会导致所有变量的声明语句,会被提升到代码的头部,这叫做变量提升(hoisting)
  3. 标识符(identifier)指的是识别各种值的合法名称。比如变量名、函数名。js标识符大小写敏感。标识符的命名规则是:只能以字母、下划线(_)和美元符号($)开头,从第二个字符开始还可以使用数字。
  4. js标识符中的字母指的是Unicode字母。因此中文标识符也可以使用
  5. js中的保留字不能用于标识符,保留字是指js中用来表示特定含义的字符,如return、class、true、false、function等
  6. 注释:注释用来对代码进行解释,js引擎执行时会忽略注释部分。//表示单行注释。/* */表示多行注释
  7. js使用大括号表示"区块"(block)。对于var命令,js的区块不构成单独的作用域(scope)
  8. 条件语句:
  • ifif...else...结构,根据条件的布尔值判断执行。

  • switch结构判断表达式的值是否与case相符并执行,如果都不符则执行最后的defaultcasebreak不能少,否则当前case代码块执行完会接着执行下一个case。switch语句部分和case语句部分,都可以使用表达式,这就是js中可以为值的地方,都可以使用表达式的体现,如下:

switch (1 + 3) {
  case 2 + 2:
    f();
    break;
  default:
    neverHappens();
}

switch语句的值和case语句的值,比较时采用的是严格相等运算符(===)

  • ?:三元运算符:如下,条件true,执行"表达式1",否则执行"表达式2",然后获取对应返回值
(条件) ? 表达式1 : 表达式2
  1. 循环语句:重复执行某个操作
  • while 循环:循环条件和代码块,条件为真,就循环执行代码块

  • for 循环:可以指定循环的起点、终点和终止条件。分为初始化表达式(initialize)、条件表达式(test)、递增表达式(increment)

for (初始化表达式; 条件; 递增表达式) {
  语句
}

所有for循环,都可以改写成while循环

for语句的无线循环表示:

for ( ; ; ){
  console.log('Hello World');
}
  • do...while 循环:先执行一次循环体,再判断条件
  • break语句用于跳出代码块或循环。continue语句用于立即终止本轮循环,并开始下一轮循环
  1. js语句的前面可以添加标签(label),相当于定位符,用于跳转到程序的任意位置
label:
  语句

数据类型

概述

  1. JavaScript的数据类型有六种(ES6新增了 Symbol 类型)
  • 数值(number):整数和小数(比如1和3.14)
  • 字符串(strin):文本(比如"Hello World")。
  • 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
  • null:表示空值,即此处的值为空。
  • 对象(object):各种值组成的集合。

数值、字符串、布尔值称为原始类型(primitive type),是最基本的数据类型。对象称为合成类型(complex type)。undefinednull两个特殊值。

对象分为:狭义的对象(object)、数组(array)、函数(function)

  1. 类型判断

typeof运算符返回一个值的数据类型:

  • 数值、字符串、布尔值分别返回numberstringboolean
  • 函数返回functionundefined返回undefined(可以检测未声明的变量),对象返回objectnull返回object
// 检测未声明
if (typeof v === "undefined") {
  // ...
}

typeof window // "object"
typeof {} // "object"
typeof [] // "object"

typeof null // "object"
  • instanceof可以区分数组和对象
[] instanceof Array // false
[] instanceof Array // true

null 和 undefined

  1. 两者含义"类似",if语句中自动转为false,相等于运算符(==)两者比较为true。null表示"空"对象,转为数值是0undefined表示"无定义"的原始值,转为数值是NaN
Number(null) // 0
Number(undefined) // NaN
  1. 函数没有返回值时,默认返回 undefined
  2. 布尔值表示truefalse两个真假状态。
  3. 如果 JavaScript 预期某个位置应该是布尔值,则会将该位置上现有的值自动转为布尔值。

下面六个值会被转为false,其他的值都是true

  • undefined
  • null
  • false
  • 0
  • NaN
  • ""或''(空字符串)

空数组([])和空对象({})对应的布尔值,都是true

数值

  1. js中所有数字都是以64位浮点数存储,整数也是如此。因此1===1.0,是同一个数
1 === 1.0 // true

JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)

  1. 浮点数不是精确的值,因此小数的比较和运算要特别注意
0.1 + 0.2 === 0.3   // false

0.3 / 0.1           // 2.9999999999999996

(0.3 - 0.2) === (0.2 - 0.1)   // false
  1. js浮点数的64个二进制位,从最左边开始,由如下组成:
  • 第1位:符号位,0表示正数,1表示负数。数值正负
  • 第2位到第12位(共11位):指数部分。数值的大小
  • 第13位到第64位(共52位):小数部分(即有效数字)。数值的精度
  1. 绝对值小于2的53次方的整数,即$-253$到$253$,都可以精确表示
Math.pow(2, 53); // 9007199254740992

Math.pow(2, 53) + 1; // 9007199254740992

Math.pow(2, 53) + 2; // 9007199254740994

Math.pow(2, 53) + 3; // 9007199254740996

Math.pow(2, 53) + 4; // 9007199254740996
  1. 最大值和最小值:Number.MAX_VALUENumber.MIN_VALUE
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
  1. 数值表示法:50(十进制)、0xFF(十六进制)、123e3123e-3(科学计数法)
  2. 当小数点前面的数字多于21位时,或者小数点后的零多于5个时,js会自动将数值转为科学计数法表示
  3. 使用字面量(literal)表示数值时:
  • 十进制:没有前导0的数值
  • 八进制:有前缀0o0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
  • 十六进制:有前缀0x0X的数值。
  • 二进制:有前缀0b0B的数值。

js会自动将八进制、十六进制、二进制转为十进制

  1. js存在2个0:+0-0,两者是等价的。唯一区别是+0-0当作分母时表达式的返回值不相等。
  2. NaN表示“非数字”(Not a Number),比如字符串解析为数字报错时会返回NaN。0除以0得到NaN。NaN只是一个特殊值,类型依旧是Number
  3. NaN不等于任何值,包括它本身
NaN === NaN // false

NaN的布尔值为false,NaN与任何数(包括它自己)的运算,得到的都是NaN

  1. Infinity表示“无穷”,表示无法表示正无穷(Infinity)和负无穷(-Infinity);非0数除以0,得到Infinity
    Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)

NaN的比较运算会返回false

  1. parseInt()方法将字符串转为整数。字符串转为整数时,会一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。转换失败返回NaN

解析科学计数法的数字时会出现奇怪的结果

第二个参数表示解析的值的进制

parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
  1. parseFloat():将一个字符串转为浮点数,可以对科学计数法字符串正确转换
  2. isNaN()判断一个值是否是NaNisNaN()只对数值有效,其他类型值会先转为数值,再判断。对于空数组和只有一个数值成员的数组,isNaN()返回false
isNaN('Hello') // true
// 相当于
isNaN(Number('Hello')) // true
  1. 判断NaN最好的方法是:NaN是唯一不等于自身的值
function myIsNaN(value) {
  return value !== value;
}

如果使用isNaN()判断,要同时判断类型:

function myIsNaN(value) {
  return typeof value === 'number' && isNaN(value);
}
  1. isFinite()判断是否为正常的数值,判断是参数也会进行类型转换

字符串

  1. 字符串是放在单引号或双引号中的零个或多个排在一起的字符。字符串中使用相同的引号要用\反斜杠转义
  2. 每行的尾部使用反斜杠,可以实现多行字符串(\后面只能有换行符,否则报错)
var longString = 'Long \
long \
long \
string';

longString // "Long long long string"

也可使用+可以将多个字符串行连接

  1. 反斜杠(\)用来表示一些特殊字符,称为转义。如\n换行符;\r:回车键;\t:制表符。如果在字符串中需要包含反斜杠,需要\转义自身。
"你好,反斜杠\\";  // "你好,反斜杠\"
  1. 字符串可以看做字符数组,但仅能使用数组的方括号运算符获取字符,而不能进行其他操作
  2. length属性返回字符串长度
'hello'.length  // 5
  1. js使用Unicode字符集。不仅以Unicode存储字符,而且可以只用Unicode码点,如'\u00A9'表示版权符号
var s = '\u00A9';
s // "©"

每个字符在 JavaScript 内部都是以16位(2个字节)的 UTF-16 格式储存。js单位字符长度固定为16位长度

js 对 UTF-16 的支持不完整,只支持两字节的字符,无法识别四字节的字符。比如四字节字符𝌆,浏览器可以正确识别是一个字符,但js无法识别,认为是两个字符。需要特别注意

'𝌆'.length // 2
  1. Base64编码:对于ASCII 码0到31的符号无法打印出来,可以使用Base64编码转换为可打印的字符;以文本格式传递二进制数据,也可以使用Base64编码
  2. Base64 是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+/这64个字符组成的可打印字符。目的是不出现特殊字符,简化程序处理。
  • btoa():任意值转为 Base64 编码
  • atob()Base64 编码转为原来的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
  1. btoa()atob()的Base64编码解码不使用非ASCII码的字符,如btoa('你好')就会报错。处理办法是加一个URI转码,如下
function b64Encode(str) {
  return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
  return decodeURIComponent(atob(str));
}

b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"

对象

  1. 对象是一组"键值对"(key-value)的集合,是一种无序的复合数据集合。

如下,对象用大括号,键值对又叫成员,分为"键名"和"键值",对应"成员的名称"和"成员的值",键名与键值用冒号分割,键值对逗号分割。

var obj = {
  foo: 'Hello',
  bar: 'World'
};
  1. 对象的所有键名都是字符串(ES6中Symbol值也可以作为键名)。键名要么符合标识名的规则,要么使用引号包含。键名又称为"属性"(property),属性可以动态创建
  2. "键值"可以是任何数据类型,比如
    属性值可以为函数,这个属性也可以称为"方法",可以像函数一样调用
    属性的值还是对象,就可以形成链式引用
  3. 对象属性之间逗号分割,最后一个属性可以加逗号(trailing comma),也可不加
  4. 不同的变量名指向同一个对象,则它们都是这个对象的引用,即指向同一个内存地址。引用只局限于对象,原始类型中,两个变量就是值的拷贝
  5. 对象采用大括号表示,则就有可能和代码块的大括号产生歧义。比如行首是大括号,如果看做表达式,则是一个对象;如果是语句,则表示一个代码区块。
{ foo: 123 }

遇到大括号的歧义时,无法确定是对象还是代码块,JavaScript引擎一律解释为代码块。

可以将大括号放入圆括号中,这样就是表达式

// eval 对字符串求值
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
  1. 使用点运算符或方括号运算符读取对象的属性,同时也可以用来赋值。
    方括号运算符的键名必须有引号,否则被看做变量
var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"

数值键名不能使用点运算符,使用方括号运算符时数值键名会转换为字符串
8. Object.keys(obj)查看一个对象所有属性或键名
9. delete用于删除对象本身的属性,成功后返回true。且删除不存在的key也返回true。只有属性存在且不能删除时,才返回false。不能删除继承的属性

delete obj.p // true
  1. in运算符检查对象是否包含某个属性。继承时属性也会返回true。
  2. hasOwnProperty(key)方法判断是否为对象自身的属性
  3. for...in循环用于遍历对象的全部属性
  • 遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
  • 不仅遍历对象自身的属性,还遍历继承的属性。

通常都是遍历对象自身的属性,因此可以结合hasOwnProperty方法

var person = { name: '阮一峰老师' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
// name

函数

  1. 函数是一段可以反复调用的代码块
  2. js中声明函数的三种方法
  • function 命令——函数的声明(Function Declaration)

如下,function 函数名(参数1,...){函数体}

function print(s) {
  console.log(s);
}
  • 函数表达式(Function Expression)

变量赋值的方式,这个匿名函数又称函数表达式

var print = function(s) {
  console.log(s);
};

函数表达式需要在语句的结尾加上分号,表示语句结束。

函数表达式声明函数时,function后面不带有函数名。如果加上函数名也仅在函数体内部有效,可用于在函数内部调用函数自身(如递归等)。

  • Function 构造函数

如下,使用Function构造函数时,可以传递任意数量的参数,只有最后一个参数会被当做函数体

var add = new Function(
  'x',
  'y',
  'return x + y'
);

// 等同于
function add(x, y) {
  return x + y;
}
  1. 函数的重复声明:如果同一个函数被多次声明,后面的就会覆盖前面的声明。
  2. 函数圆括号在声明时用于放入参数;非声明时函数后紧跟圆括号,表示调用函数并传入参数。return语句表示返回后面表达式的值,并退出当前函数
  3. 函数调用自身,就是递归(recursion)
  4. js将函数看作一种值。凡是可以使用值的地方,就能使用函数。函数是一个可以执行的值。因为函数与其他数据类型地位平等,所有在js中又称函数为第一等公民
  5. js将函数名视为变量名,所以同样有函数名的提升,function声明函数时会被提升到代码头部。但是赋值语句定义函数,在赋值前调用会报错(因为是undefined)
  6. 函数的属性和方法
  • name属性返回函数的名字
  • length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
    length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的“方法重载”(overload)
  • toString()方法返回字符串,内容是函数的源码。函数内的注释也会返回。
    原生函数,toString()方法返回function (){[native code]}
  1. 函数作用域:作用域(scope)指变量存在的范围。ES5中,JavaScript只有两种作用域:全局作用域(变量在整个程序中一直存在);函数作用域(变量只在函数内部存在),函数作用域内同样存在变量提升。ES6新增了块级作用域
  2. 对于顶层函数(直接在js文件中或<script>标签中),函数外部声明的变量就是全局变量(global variable)。函数内部定义的变量,外部无法读取,称为"局部变量"(local variable),且函数内部定义的局部变量,在当前函数作用内会覆盖同名全局变量
  3. 使用var声明的变量只有在函数内才是局部变量,其他区块内声明仍是全局变量(比如ifwhile等块内)
  4. 函数本身的作用域:函数本身也是一个值。它的作用域与变量一样,是其声明时所在的作用域,与其运行时所在的作用域无关函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域
var a = 1;
var x = function () {
  console.log(a); //声明时所在作用域的变量a
};

function f() {
  var a = 2;
  x();
}
f() // 1

同样,函数体内部声明的函数,作用域绑定函数体内部。

function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f() // 1

如上,fon颞部声明的函数bar,bar的作用域为foo函数内部,当在foo外部取出bar执行时,bar中使用的变量x指向的是foo内部(声明时所在作用域)的x,而不是foo外部的x。这就是闭包现象

  1. 闭包(closure)就是能够读取其他函数内部变量的函数。而js中,只有函数内部的子函数才能读取内部变量,因此闭包简单理解就是"定义在一个函数内部的函数"。本质上,闭包用于将函数内部和函数外部连接起来。

理解闭包,要先理解变量的作用域,函数内部可以读取全局变量

var e = "我是全局变量";

function f1() {
  console.log(e);
}
f1() // "我是全局变量"

但是,函数外部无法读取函数内部声明的变量。

function f1() {
  var n = "我是函数内部变量";
}

console.log(n)  // Uncaught ReferenceError: n is not defined

但是,正常无法得到函数内的局部变量,如果想要实现必须通过变通方法:在函数的内部,再定义一个函数。这样函数内部的子函数就可以使用函数内的局部变量,但是函数内无法使用其子函数内的局部变量。这就是js特有的 "链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。

父对象的所有变量,对子对象都是可见的,反之则不成立。

这样将子函数作为返回值,就可以在函数外部读取它的内部变量

function f1() {
  var n = "我是函数内部变量";
  function f2() {
  console.log(n); // "我是函数内部变量"
  }
  return f2;
}

var result=f1();
result(); // "我是函数内部变量"

如上,获取f1的返回值f2函数,而f2可以读取f1的内部变量,这样就可以在外部获取f1内部的变量。闭包就是函数f2

闭包最大的特点,就是它可以"记住"诞生的环境

闭包的用处:

  • 读取函数内部的变量之外
  • 让变量始终保持在内存中(即闭包可以使得它的诞生环境一直存在)
function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5  // 闭包使得start变量一直在内存中
inc() // 6
inc() // 7

闭包使得函数内部环境一直存在,闭包可以看作是函数内部作用域的一个接口

  • 封装对象的私有属性和私有方法
function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('张三');
p1.setAge(25);
p1.getAge(); // 25
p1.name;     // "张三"

如上,函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量。

比如下面,就是闭包使用的典型例子。通常其在处理循环页面dom元素并添加事件方法时需要特别注意

var funs=[];
for(var i=0;i<5;i++){
  funs[i]=function(){
    console.log(i);
  }
}
funs.forEach(function(f){ 
  f(); 
})
// 输出 5个5,而不是0,1,2,3,4

利用闭包,可实现将变量i的每一个循环值"保存",供调用时输出

var funs=[];
for(var i=0;i<5;i++){
  (function(i){
    funs[i]=function(){
      console.log(i);
    }
  })(i);
}
funs.forEach(function(f){ 
  f(); 
})
// 输出 0,1,2,3,4
  1. 不能滥用闭包,容易产生网页性能问题。外层函数每次运行,都会生成一个新的闭包,而每个闭包都会保留外层函数的内部变量,内存消耗很大。
  2. 函数的参数,通过圆括号传递外部数据。js在调用时允许省略参数(省略的参数变为undefined,但只能省略在后面的参数,靠前的参数不能省略)
  3. 参数传递方式:函数参数如果是原始类型的值(数值、字符串、布尔值),参数传递就是传值传递(passes by value)。函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference),此时在函数内部修改参数,会影响原始值
  4. arguments对象包含了函数运行时的所有参数,arguments[0]是第一个参数,arguments[1]是第二个参数,以此类推,arguments只能在函数体内部使用。

argumentslength属性可以判断函数调用时的参数个数

arguments是对象,只是很像数组。如果想使用数组方法,需要将arguments转为数组。下面是转换为数组的两种方法:

var args = Array.prototype.slice.call(arguments);

// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}
  1. 立即调用的函数表达式(IIFE):

圆括号()运算符跟在函数名后面,表示调用该函数,如果在定义函数之后,立即调用该函数,如下当function出现在行首时,js会将其解释为语句,后面是函数的定义,这是以圆括号结尾就会报错。

function(){ /* code */ }();

function出现在行首就是语句。

// 语句
function f() {}

// 表达式
var f = function f() {}

解决办法就是不要让function出现在行首,让js引擎解释为表达式。这样就可以在定义函数之后立即运行

比如放圆括号中:

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

这就是"立即调用的函数表达式"(Immediately-Invoked Function Expression,IIFE)。

IIFE最后的分号是必须的。尤其是两个IIFE写在一起时,如果省略分号,连着的两个IIFE就出出现问题,可能会报错

如下,两行之间没有分号,js将它们连在一起解释,将第二行解释为第一行的参数

// 报错
(function(){ /* code */ }())
(function(){ /* code */ }())

任何让解释器以表达式来处理函数定义的方法,都能产生IIFE

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

// 甚至
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

通常,只对匿名函数使用"立即执行的函数表达式"。目的:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

如下,写法二完全避免了污染全局变量

// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);

// 写法二
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

数组

  1. 数组(array)是按次序排列的一组值,位置编号从0开始,用方括号表示。比如var arr = ['a', 'b', 'c'];
  2. 任何类型的数据都可以放入数组
var arr = [
  {a: 1},
  [1, 2, 3],
  function() {return true;}
];

arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}
  1. 数组本质是一种特殊的对象。typeof返回数组的类型是object

数组"对象"的键名,是按次序排列的整数。

var arr = ['a', 'b', 'c'];

Object.keys(arr); // ["0", "1", "2"]

js中,对象的键名一律为字符串,数组的键名也是字符串。对象中数值的键名不能使用点结构(object.key),所以数组元素不能使用.获取

  1. 数组的length属性,返回数组元素的个数,即数组长度

js使用一个32位整数,保存数组的元素个数。即数组长度最多只有 4294967295 个($2^32 - 1$)个

length属性是可写的。减少length值可用于删除数组元素。length设置为0可用于清空数组。length属性的值等于最大的数字键加1

var arr = [ 'a', 'b', 'c' ];
arr.length // 3

arr.length = 2;
arr // ["a", "b"]
  1. 不推荐使用for...in遍历数组(会遍历非数字键)。可使用forwhile循环,或forEach方法遍历
var a = [1, 2, 3];

// for循环
for(var i = 0; i < a.length; i++) {
  console.log(a[i]);
}

// while循环
var i = 0;
while (i < a.length) {
  console.log(a[i]);
  i++;
}

var l = a.length;
while (l--) {
  console.log(a[l]);
}

// forEach方法
a.forEach(function (item) {
  console.log(item);
});
  1. 数组的某个位置是空元素,即两个逗号之间没有任何值,称该数组存在空位(hole)
var a = [1, , 3 , 4];
a.length // 4

a[1] // undefined

delete a[2];
a[2] // undefined
a.length // 4

数组的空位不影响length属性。数组最后一个元素后面有逗号,不会产生空位。空位返回undefineddelete命令删除一个数组成员会形成空位,且不会影响length

  1. 类似数组的对象:如果一个对象的所有键名都是正整数或零,并且有length属性,则这个对象语法上称为"类似数组的对象"(array-like object)。

"类似数组的对象"并不是数组,它们不具备数组特有的方法。

如下,obj就是一个类似数组的对象

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function

"类似数组的对象"的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但这种length属性不是动态值

典型的"类似数组的对象"是函数的arguments对象,以及大多数DOM元素集,还有字符串。

// arguments对象
function args() { return arguments }
var arrayLike = args('a', 'b');

arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false

// 字符串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false

数组的slice方法可以将"类似数组的对象"变成真正的数组。

var arr = Array.prototype.slice.call(arrayLike);

除了转为真正的数组,还可以通过call()把数组的方法放到对象上面,从而让"类似数组的对象"可以使用数组的方法。

Array.prototype.forEach.call(arrayLike, function (value, index) {
  console.log(index + ' : ' + value);
});

这种方法比直接使用数组原生的forEach要慢,所以可以先转为真正的数组,在调用方法

posted @ 2020-07-29 22:42  findmoon  阅读(514)  评论(0编辑  收藏  举报