1. 七种数据类型

  1. 数值(number):整数和小数(比如13.14
  2. 字符串(string):文本(比如Hello World
  3. 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
  4. undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
  5. null:表示空值,即此处的值为空
  6. 对象(object):各种值组成的集合
  7. symbol
  • 基本数据类型:number、string、bookean、symbol
  • 空类型:undefinged、null
  • 容器数据类型:object
    • 狭义的对象(object)
    • 数组(array)
    • 函数(function)

查看值的类型

  1. // typeof 用法
  2. typeof 123 // "number"
  3. typeof '123' // "string"
  4. typeof false // "boolean"
  5. function f() {}
  6. typeof f // "function"
  7. typeof undefined // "undefined"
  8. typeof window // "object"
  9. typeof {} // "object"
  10. typeof [] // "object"
  11. typeof null // "object" // null的类型是object,这是由于历史原因造成的
  12. // typeof 在判断语句下的使用
  13. if (typeof v === "undefined") {
  14. // ...
  15. }
  16. // instanceof 用法
  17. var o = {};
  18. var a = [];
  19. o instanceof Array // false
  20. a instanceof Array // true

2. null,undefined 和 布尔值

null与undefined都可以表示“没有”,含义非常相似

  1. // 语法效果几乎没区别
  2. // 同时设置两个这样的值与历史原因有关
  3. var a = undefined;
  4. var a = null;
  5. // null 转数字
  6. Number(null) // 0
  7. 5 + null // 5
  8. // undefined 转数字
  9. Number(undefined) // NaN
  10. 5 + undefined // NaN
  11. // 这样理解 null 和 defined 用法和含义
  12. // null表示空值,即该处的值现在为空
  13. // undefined表示“未定义”,下面是返回undefined的典型场景
  14. var i; // 变量声明了,但没有赋值
  15. i // undefined
  16. function f(x) { // 调用函数时,应该提供的参数没有提供,该参数等于 undefined
  17. return x;
  18. }
  19. f() // undefined
  20. var o = new Object(); // // 对象没有赋值的属性
  21. o.p // 函数没有返回值时,默认返回 undefined
  22. function f() {}
  23. f() // undefined

布尔值

  • “真”用关键字true表示,“假”用关键字false表示
  • 如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值
  1. // 5 个会被转为 false 的值
  2. undefined
  3. null
  4. false
  5. 0
  6. NaN
  7. '' ""
  8. // 注意,空数组([])和空对象({})对应的布尔值,都是true

3. 数值

  • JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此
  • 由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心
  • 与数值相关的全局方法
    • parseInt()
    • parseFloat()
    • isNaN()
    • isFinite()
  1. Math.pow(2, 53) + 2 // 9007199254740994
  2. Math.pow(2, 53) + 3 // 9007199254740996
  3. Math.pow(2, 53) + 4 // 9007199254740996
  4. // 大于2的53次方以后,多出来的有效数字(最后三位的111)都会无法保存,变成0
  5. // 所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理
  6. Math.pow(2, 1024) // Infinity
  7. // 如果一个数大于等于2的1024次方,那么就会发生“正向溢出”
  8. Math.pow(2, -1075) // 0
  9. // 如果一个数小于等于2的-1075次方,那么就会发生为“负向溢出”
  10. // JavaScript 提供Number对象的MAX_VALUE和MIN_VALUE属性,返回可以表示的具体的最大值和最小值。
  11. Number.MAX_VALUE // 1.7976931348623157e+308
  12. Number.MIN_VALUE // 5e-324

数值的进制

  1. // 默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制
  2. 0xff // 255
  3. 0o377 // 255
  4. 0b11 // 3
  5. 0xzz // 报错
  6. 0o88 // 报错
  7. 0b22 // 报错

正零和负零

  1. -0 === +0 // true
  2. 0 === -0 // true
  3. 0 === +0 // true
  4. +0 // 0
  5. -0 // 0
  6. (-0).toString() // '0'
  7. (+0).toString() // '0'
  8. (1 / +0) === (1 / -0) // false
  9. // 因为除以正零得到+Infinity,除以负零得到-Infinity

NaN

  • 主要出现在将字符串解析成数字出错的场合
  1. 5 - 'x' // NaN
  2. Math.acos(2) // NaN
  3. Math.log(-1) // NaN
  4. Math.sqrt(-1) // NaN
  5. 0 / 0 // NaN
  6. typeof NaN // 'number'
  7. NaN === NaN // false
  8. Boolean(NaN) // false
  9. NaN + 32 // NaN
  10. NaN - 32 // NaN
  11. NaN * 32 // NaN
  12. NaN / 32 // NaN

parseInt()

  1. parseInt方法用于将字符串转为整数
  2. parseInt('123') // 123
  3. // 如果字符串头部有空格,空格会被自动去除
  4. parseInt(' 81') // 81
  5. // 如果parseInt的参数不是字符串,则会先转为字符串再转换
  6. parseInt(1.23) // 1
  7. // 等同于
  8. parseInt('1.23') // 1
  9. // parseInt的参数都是字符串,结果只返回字符串头部可以转为数字的部分
  10. parseInt('8a') // 8
  11. parseInt('12**') // 12
  12. parseInt('12.34') // 12
  13. parseInt('15e2') // 15
  14. parseInt('15px') // 15
  15. // 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
  16. parseInt('abc') // NaN
  17. parseInt('.3') // NaN
  18. parseInt('') // NaN
  19. parseInt('+') // NaN
  20. parseInt('+1') // 1
  21. // 如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析
  22. // 如果字符串以0开头,将其按照10进制解析
  23. parseInt('0x10') // 16
  24. // 进制转换
  25. parseInt('1000') // 1000
  26. parseInt('1000', 2) // 8
  27. parseInt('1000', 6) // 216
  28. parseInt('1000', 8) // 512
  29. // 第二个参数表示被解析的值的进制
  30. // 第二个参数只有在2到36之间,超出这个范围,则返回NaN
  31. // 如果第二个参数是0、undefined和null,则直接忽略
  32. parseInt('10', 37) // NaN
  33. parseInt('10', 1) // NaN
  34. parseInt('10', 0) // 10
  35. parseInt('10', null) // 10
  36. parseInt('10', undefined) // 10

parseFloat()

  1. parseFloat('3.14') // 3.14
  2. parseFloat('314e-2') // 3.14
  3. parseFloat('0.0314E+2') // 3.14
  4. parseFloat('3.14more non-digit characters') // 3.14
  5. parseFloat([]) // NaN
  6. parseFloat('FF2') // NaN
  7. parseFloat('') // NaN
  8. // 尤其值得注意,parseFloat会将空字符串转为NaN

isNaN()

  • 使用isNaN之前,最好判断一下数据类型
  1. // isNaN只对数值有效,如果传入其他值,会被先转成数值
  2. isNaN(NaN) // true
  3. isNaN(123) // false
  4. isNaN('Hello') // true
  5. isNaN({}) // true
  6. isNaN(['xzy']) // true
  7. // 对于空数组和只有一个数值成员的数组,isNaN返回false
  8. // 原因是这些数组能被Number函数转成数值
  9. isNaN([]) // false
  10. isNaN([123]) // false
  11. isNaN(['123']) // false

isFinite()

  • 返回一个布尔值,表示某个值是否为正常的数值
  1. isFinite(Infinity) // false
  2. isFinite(-Infinity) // false
  3. isFinite(NaN) // false
  4. isFinite(undefined) // false
  5. isFinite(null) // true
  6. isFinite(-1) // true
  7. // 除了Infinity、-Infinity、NaN和undefined这几个值会返回false
  8. // isFinite对于其他的数值都会返回true。

4. 字符串

  1. // 合法字符串
  2. 'abc'
  3. "abc"
  4. 'key = "value"'
  5. "It's a long journey"
  6. 'Did she say \'Hello\'?'
  7. "Did she say \"Hello\"?"
  8. // 分多行会报错
  9. 'a
  10. b
  11. c'
  12. // // SyntaxError: Unexpected token ILLEGAL
  13. // 若要分行,每一行尾部加 \
  14. var longString = 'Long \
  15. long \
  16. long \
  17. string';
  18. longString
  19. // "Long long long string"
  20. // 连接运算符(+)可以连接多个单行字符串
  21. var longString = 'Long '
  22. + 'long '
  23. + 'long '
  24. + 'string';
  25. // 转义
  26. \0 null\u0000
  27. \b :后退键(\u0008
  28. \f :换页符(\u000C
  29. \n :换行符(\u000A
  30. \r :回车键(\u000D
  31. \t :制表符(\u0009
  32. \v :垂直制表符(\u000B
  33. \' :单引号(\u0027
  34. \" :双引号(\u0022
  35. \\ :反斜杠(\u005C

字符串可以被视为字符数组,因此可以使用数组的方括号运算符

  1. var s = 'hello';
  2. s[0] // "h"
  3. s[1] // "e"
  4. s[4] // "o"

length 属性

  1. // length 属性无法改变
  2. var s = 'hello';
  3. s.length // 5
  4. s.length = 3;
  5. s.length // 5
  6. s.length = 7;
  7. s.length // 5

Base64 转码

  • 主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理
  1. // btoa():任意值转为 Base64 编码
  2. // atob():Base64 编码转为原来的值
  3. var string = 'Hello World!';
  4. btoa(string) // "SGVsbG8gV29ybGQh"
  5. atob('SGVsbG8gV29ybGQh') // "Hello World!"
  6. // 这两个方法不适合非 ASCII 码的字符,会报错
  7. btoa('你好') // 报错
  8. // 要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节
  9. function b64Encode(str) {
  10. return btoa(encodeURIComponent(str));
  11. }
  12. function b64Decode(str) {
  13. return decodeURIComponent(atob(str));
  14. }
  15. b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
  16. b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"

5. 对象

  • 一组“键值对”(key-value)的集合
  • 对象的所有键名都是字符串,如果键名是数值,会被自动转为字符串

  1. var obj = {
  2. foo: 'Hello',
  3. bar: 'World'
  4. };
  5. // 如果属性的值还是一个对象,就形成了链式引用
  6. var o1 = {};
  7. var o2 = { bar: 'hello' };
  8. o1.foo = o2;
  9. o1.foo.bar // "hello"

表达式还是语句?

  1. // 歧义的代码
  2. // 如果是一个表达式,表示一个包含foo属性的对象
  3. // 如果是一个语句,表示一个代码区块,里面有一个标签foo,指向表达式123
  4. { foo: 123 }
  5. // JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块
  6. // 如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式
  7. ({ foo: 123 }) // 正确
  8. ({ console.log(123) }) // 报错

属性的读取

  1. // 点运算符和方括号运算符
  2. var obj = {
  3. p: 'Hello World'
  4. };
  5. obj.p // "Hello World"
  6. obj['p'] // "Hello World"
  7. // 方括号里用变量
  8. var foo = 'bar';
  9. var obj = {
  10. foo: 1,
  11. bar: 2
  12. };
  13. obj.foo // 1
  14. obj[foo] // 2
  15. // 方括号运算符内部还可以使用表达式
  16. obj['hello' + ' world']
  17. obj[3 + 3]
  18. // 数字键可以不加引号,因为会自动转成字符串
  19. var obj = {
  20. 0.7: 'Hello World'
  21. };
  22. obj['0.7'] // "Hello World"
  23. obj[0.7] // "Hello World"

属性的赋值

  1. var obj = {};
  2. obj.foo = 'Hello';
  3. obj['bar'] = 'World';
  4. var obj = { p: 1 };
  5. // 等价于
  6. var obj = {};
  7. obj.p = 1;

属性的查看

  1. // 返回一个数组
  2. var obj = {
  3. key1: 1,
  4. key2: 2
  5. };
  6. Object.keys(obj);
  7. // ['key1', 'key2']

属性的删除:delete 命令

  1. // delete命令用于删除对象的属性,删除成功后返回 true
  2. var obj = { p: 1 };
  3. Object.keys(obj) // ["p"]
  4. delete obj.p // true
  5. obj.p // undefined
  6. Object.keys(obj) // []
  7. // 注意,删除一个不存在的属性,delete不报错,而且返回true
  8. var obj = {};
  9. delete obj.p // true

属性是否存在:in 运算符

  1. // in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的
  2. var obj = { p: 1 };
  3. 'p' in obj // true
  4. 'toString' in obj // true
  5. // 对象的hasOwnProperty方法判断一下,是否为对象自身的属性
  6. var obj = {};
  7. if ('toString' in obj) {
  8. console.log(obj.hasOwnProperty('toString')) // false
  9. }

属性的遍历:for…in 循环

  1. var obj = {a: 1, b: 2, c: 3};
  2. for (var i in obj) {
  3. console.log('键名:', i);
  4. console.log('键值:', obj[i]);
  5. }
  6. // 使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性
  7. var person = { name: '老张' };
  8. for (var key in person) {
  9. if (person.hasOwnProperty(key)) {
  10. console.log(key);
  11. }
  12. }

with 语句

  • 操作同一个对象的多个属性时,提供一些书写的方便 ```javascript // 例一 var obj = { p1: 1, p2: 2, }; with (obj) { p1 = 4; p2 = 5; } // 等同于 obj.p1 = 4; obj.p2 = 5;

// 例二 with (document.links[0]){ console.log(href); console.log(title); console.log(style); } // 等同于 console.log(document.links[0].href); console.log(document.links[0].title); console.log(document.links[0].style);

// 注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性 // 否则会创造一个当前作用域的全局变量

  1. <a name="1FdR9"></a>
  2. ### 6. 函数
  3. 函数声明
  4. ```javascript
  5. // function 命令
  6. function print(s) {
  7. console.log(s);
  8. }
  9. // 函数表达式
  10. var print = function(s) {
  11. console.log(s);
  12. };
  13. // Function 构造函数
  14. var add = new Function(
  15. 'x',
  16. 'y',
  17. 'return x + y'
  18. );
  19. // 等同于
  20. function add(x, y) {
  21. return x + y;
  22. }
  23. // 以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体
  24. // 如果只有一个参数,该参数就是函数体

如果同一个函数被多次声明,后面的声明就会覆盖前面的声明

  1. function f() {
  2. console.log(1);
  3. }
  4. f() // 2
  5. function f() {
  6. console.log(2);
  7. }
  8. f() // 2

圆括号运算符,return 语句和递归

  • 函数体内部的return语句,表示返回
  • JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值
  • 后面即使还有语句,也不会得到执行
  • 也就是说,return语句所带的那个表达式,就是函数的返回值
  • return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined
  1. function add(x, y) {
  2. return x + y;
  3. }
  4. add(1, 1) // 2
  5. // 函数可以调用自身,这就是递归(recursion)。下面就是通过递归,计算斐波那契数列的代码
  6. function fib(num) {
  7. if (num === 0) return 0;
  8. if (num === 1) return 1;
  9. return fib(num - 2) + fib(num - 1);
  10. }
  11. fib(6) // 8

第一等公民

  • JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同

函数名的提升

  • JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时
  • 整个函数会像变量声明一样,被提升到代码头部
  1. f();
  2. function f() {}
  3. // 采用赋值语句定义函数,JavaScript 就会报错
  4. f();
  5. var f = function (){};
  6. // TypeError: undefined is not a function
  7. // 用function命令和var赋值语句声明同一个函数,由于存在函数提升,最后会采用var赋值语句的定义
  8. var f = function () {
  9. console.log('1');
  10. }
  11. function f() {
  12. console.log('2');
  13. }
  14. f() // 1

函数的属性和方法

  1. // name 属性
  2. function f1() {}
  3. f1.name // "f1"
  4. length 属性,函数预期传入的参数个数
  5. function f(a, b) {}
  6. f.length // 2
  7. // toString()方法返回一个字符串,内容是函数的源码
  8. function f() {
  9. a();
  10. b();
  11. c();
  12. }
  13. f.toString()
  14. // function f() {
  15. // a();
  16. // b();
  17. // c();
  18. // }
  19. // 对于那些原生的函数,toString()方法返回function (){[native code]}
  20. Math.sqrt.toString() // "function sqrt() { [native code] }"
  21. // 函数内部的注释也可以返回
  22. function f() {/*
  23. 这是一个
  24. 多行注释
  25. */}
  26. f.toString()
  27. // "function f(){/*
  28. // 这是一个
  29. // 多行注释
  30. // */}"
  31. // 利用这一点,可以变相实现多行字符串
  32. var multiline = function (fn) {
  33. var arr = fn.toString().split('\n');
  34. return arr.slice(1, arr.length - 1).join('\n');
  35. };
  36. function f() {/*
  37. 这是一个
  38. 多行注释
  39. */}
  40. multiline(f);
  41. // " 这是一个
  42. // 多行注释"

函数作用域

  • ES5 中,JavaScript 只有两种作用域,全局作用域和函数作用域
  • ES6 新增了块级作用域
  • 对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取
  • 在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)
  • 对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量
  1. var v = 1;
  2. function f() {
  3. console.log(v);
  4. }
  5. f()
  6. // 1

函数内部的变量提升

  • 与全局作用域一样,函数作用域内部也会产生“变量提升”现象
  • var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
  1. function foo(x) {
  2. if (x > 100) {
  3. var tmp = x - 100;
  4. }
  5. }
  6. // 等同于
  7. function foo(x) {
  8. var tmp;
  9. if (x > 100) {
  10. tmp = x - 100;
  11. };
  12. }

函数本身的作用域

  • 函数本身也是一个值,也有自己的作用域。它的作用域与变量一样
  • 就是其声明时所在的作用域,与其运行时所在的作用域无关
  1. // 函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值
  2. var a = 1;
  3. var x = function () {
  4. console.log(a);
  5. };
  6. function f() {
  7. var a = 2;
  8. x();
  9. }
  10. f() // 1
  11. // 函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错
  12. var x = function () {
  13. console.log(a);
  14. };
  15. function y(f) {
  16. var a = 2;
  17. f();
  18. }
  19. y(x)
  20. // ReferenceError: a is not defined
  21. // 同样地,函数体内部声明的函数,作用域绑定函数体内部
  22. function foo() {
  23. var x = 1;
  24. function bar() {
  25. console.log(x);
  26. }
  27. return bar;
  28. }
  29. var x = 2;
  30. var f = foo();
  31. f() // 1
  32. // 函数foo内部声明了一个函数bar,bar的作用域绑定foo
  33. // 当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x
  34. // 正是这种机制,构成了“闭包”现象

参数

  • 函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数
  1. // 参数允许省略
  2. function f(a, b) {
  3. return a;
  4. }
  5. f(1, 2, 3) // 1
  6. f(1) // 1
  7. f() // undefined
  8. f.length // 2
  9. // 函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数

参数传递

  • 函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)
    • 这意味着,在函数体内修改参数值,不会影响到函数外部
  • 如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)
    • 也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值
  • 如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
  1. // 在函数内部,p的值是原始值的拷贝,无论怎么修改,都不会影响到原始值
  2. var p = 2;
  3. function f(p) {
  4. p = 3;
  5. }
  6. f(p);
  7. p // 2
  8. // 在函数内部修改obj的属性p,会影响到原始值
  9. var obj = { p: 1 };
  10. function f(o) {
  11. o.p = 2;
  12. }
  13. f(obj);
  14. obj.p // 2
  15. // 在函数f()内部,参数对象obj被整个替换成另一个值。这时不会影响到原始值
  16. // 这是因为,形式参数(o)的值实际是参数obj的地址
  17. // 重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响
  18. var obj = [1, 2, 3];
  19. function f(o) {
  20. o = [2, 3, 4];
  21. }
  22. f(obj);
  23. obj // [1, 2, 3]

同名参数

  • 如果有同名的参数,则取最后出现的那个值
  1. function f(a, a) {
  2. console.log(a);
  3. }
  4. f(1, 2) // 2
  5. function f(a, a) {
  6. console.log(a);
  7. }
  8. f(1) // undefined
  9. function f(a, a) {
  10. console.log(arguments[0]);
  11. }
  12. f(1) // 1

arguments 对象

  • 可以在函数体内部读取所有参数
  • arguments对象包含了函数运行时的所有参数
  • arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推
  • 这个对象只有在函数体内部,才可以使用
  1. var f = function (one) {
  2. console.log(arguments[0]);
  3. console.log(arguments[1]);
  4. console.log(arguments[2]);
  5. }
  6. f(1, 2, 3)
  7. // 1
  8. // 2
  9. // 3
  10. // 正常模式下,arguments对象可以在运行时修改
  11. var f = function(a, b) {
  12. arguments[0] = 3;
  13. arguments[1] = 2;
  14. return a + b;
  15. }
  16. f(1, 1) // 5
  17. 严格模式下,arguments对象与函数参数不具有联动关系
  18. var f = function(a, b) {
  19. 'use strict'; // 开启严格模式
  20. arguments[0] = 3;
  21. arguments[1] = 2;
  22. return a + b;
  23. }
  24. f(1, 1) // 2

arguments 与 数组的关系

  • 虽然arguments很像数组,但它是一个对象
  • 数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用
  1. var args = Array.prototype.slice.call(arguments);
  2. // 或者
  3. var args = [];
  4. for (var i = 0; i < arguments.length; i++) {
  5. args.push(arguments[i]);
  6. }

闭包

  • 闭包(closure)是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现
  • 得到函数内的局部变量的方法,在函数的内部,再定义一个函数
  • 闭包的最大用处有两个
    • 一个是可以读取函数内部的变量
    • 另一个就是让这些变量始终保持在内存中
  • 闭包的另一个用处,是封装对象的私有属性和私有方法
  • 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量
  • 所以内存消耗很大,因此不能滥用闭包,否则会造成网页的性能问题 ```javascript // 闭包就是函数f2,即能够读取其他函数内部变量的函数 function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999

// 闭包使得内部变量记住上一次调用时的运算结果 function createIncrementor(start){ return function(){ return start++ } } var inc = createIncrementor(5); inc() // 5 inc() // 6 inc() // 7 // start是函数createIncrementor的内部变量 // 通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算

function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { name: name, getAge: getAge, setAge: setAge }; }

// 函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量 var p1 = Person(‘张三’); p1.setAge(25); p1.getAge() // 25

  1. 立即调用的函数表达式
  2. - function 这个关键字即可以当作语句,也可以当作表达式
  3. - 为了避免解析上的歧义,JavaScript 引擎规定,如果function关键字出现在行首,一律解释成语句
  4. - 引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面
  5. ```javascript
  6. function(){ /* code */ }();
  7. // SyntaxError: Unexpected token (
  8. // 语句
  9. function f() {}
  10. // 表达式
  11. var f = function f() {}
  12. // 引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误
  13. (function(){ /* code */ }());
  14. // 或者
  15. (function(){ /* code */ })();
  16. // 这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE
  17. // 上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错
  18. // 如果没有分号,JavaScript 会将它们连在一起解释,将第二行解释为第一行的参数
  19. // 推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果
  20. var i = function(){ return 10; }();
  21. true && function(){ /* code */ }();
  22. 0, function(){ /* code */ }();
  23. !function () { /* code */ }();
  24. ~function () { /* code */ }();
  25. -function () { /* code */ }();
  26. +function () { /* code */ }();
  27. // 通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:
  28. // 一是不必为函数命名,避免了污染全局变量
  29. // 二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
  30. (function () {
  31. var tmp = newData;
  32. processData(tmp);
  33. storeData(tmp);
  34. }());

eval 命令

  • eval命令接受一个字符串作为参数,并将这个字符串当作语句执行
  • eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题
  • 不推荐使用
  1. eval('var a = 1;');
  2. a // 1
  3. eval('3x') // Uncaught SyntaxError: Invalid or unexpected token
  4. // eval命令修改了外部变量a的值
  5. var a = 1;
  6. eval('a = 2');
  7. a // 2

7. 数组

  • 数组的本质属于一种特殊的对象。typeof运算符会返回数组的类型是object
  • 数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)
  1. length 属性是一个动态的值,等于键名中的最大整数加上1
  2. ['a', 'b', 'c'].length // 3
  3. var arr = ['a', 'b'];
  4. arr.length // 2
  5. arr[2] = 'c';
  6. arr.length // 3
  7. arr[9] = 'd';
  8. arr.length // 10
  9. arr[1000] = 'e';
  10. arr.length // 1001
  11. // 数组的数字键不需要连续,length属性的值总是比最大的那个整数键大1
  12. // 如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length设置的值
  13. var arr = [ 'a', 'b', 'c' ];
  14. arr.length // 3
  15. arr.length = 2;
  16. arr // ["a", "b"]
  17. // 清空数组的一个有效方法,就是将length属性设为0
  18. var arr = [ 'a', 'b', 'c' ];
  19. arr.length = 0;
  20. arr // []
  21. var a = ['a'];
  22. a.length = 3;
  23. a[1] // undefined

in 运算符

  1. var arr = [ 'a', 'b', 'c' ];
  2. 2 in arr // true
  3. '2' in arr // true
  4. 4 in arr // false
  5. var arr = [];
  6. arr[100] = 'a';
  7. 100 in arr // true
  8. 1 in arr // false

for…in 循环和数组的遍历

  1. // for..in
  2. var a = [1, 2, 3];
  3. for (var i in a) {
  4. console.log(a[i]);
  5. }
  6. // for循环
  7. for(var i = 0; i < a.length; i++) {
  8. console.log(a[i]);
  9. }
  10. // while循环
  11. var i = 0;
  12. while (i < a.length) {
  13. console.log(a[i]);
  14. i++;
  15. }
  16. // while逆向遍历
  17. var l = a.length;
  18. while (l--) {
  19. console.log(a[l]);
  20. }
  21. // forEach 方法遍历数组
  22. var colors = ['red', 'green', 'blue'];
  23. colors.forEach(function (color) {
  24. console.log(color);
  25. });
  26. // red
  27. // green
  28. // blue

数组的空位

  1. var a = [1, , 1];
  2. a.length // 3
  3. var a = [1, 2, 3,];
  4. a.length // 3
  5. a // [1, 2, 3]
  6. var a = [, , ,];
  7. a[1] // undefined
  8. var a = [1, 2, 3];
  9. delete a[1];
  10. a[1] // undefined
  11. a.length // 3
  12. // length属性不过滤空位
  13. // 空位和undefined,是不一样的
  14. // 数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过
  15. var a = [, , ,];
  16. a.forEach(function (x, i) {
  17. console.log(i + '. ' + x);
  18. }) // 不产生任何输出
  19. for (var i in a) {
  20. console.log(i);
  21. } // 不产生任何输出
  22. Object.keys(a) // []
  23. // 如果某个位置是undefined,遍历的时候就不会被跳过
  24. var a = [undefined, undefined, undefined];
  25. a.forEach(function (x, i) {
  26. console.log(i + '. ' + x);
  27. });
  28. // 0. undefined
  29. // 1. undefined
  30. // 2. undefined
  31. for (var i in a) {
  32. console.log(i);
  33. }
  34. // 0
  35. // 1
  36. // 2
  37. Object.keys(a)
  38. // ['0', '1', '2']
  39. // 空位就是数组没有这个元素,所以不会被遍历到
  40. // 而undefined则表示数组有这个元素,值是undefined,所以遍历不会跳过

类似数组的对象

  • 键名都是正整数或零,并且有length属性
  1. // 典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串
  2. // arguments对象
  3. function args() { return arguments }
  4. var arrayLike = args('a', 'b');
  5. arrayLike[0] // 'a'
  6. arrayLike.length // 2
  7. arrayLike instanceof Array // false
  8. // DOM元素集
  9. var elts = document.getElementsByTagName('h3');
  10. elts.length // 3
  11. elts instanceof Array // false
  12. // 字符串
  13. 'abc'[1] // 'b'
  14. 'abc'.length // 3
  15. 'abc' instanceof Array // false
  16. // 调用数组的 slice 方法可以将“类似数组的对象”变成真正的数组
  17. // 即调用它的截取片段
  18. var arr = Array.prototype.slice.call(arrayLike);
  19. // 调用数组的 forEach 方法
  20. Array.prototype.forEach.call(arrayLike, print);
  21. // 这种方法比直接使用数组原生的forEach要慢
  22. // 所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法

「@浪里淘沙的小法师」