JavaScript 标准参考教程(alpha)笔记——语法

JavaScript 标准参考教程(alpha)笔记——语法

http://javascript.ruanyifeng.com/#introduction

2.1.4 n--: 先使用n再执行n=n-1; --n: 在使用n之前先执行n=n-1;

2.1.5 区块( {   } )对于var命令不构成单独的作用域,与不使用区块的情况没有任何区别。

2.1.6.3 若switch结构中case代码块内部没有break语句,否则就会接下去执行下一个case代码。

需要注意的是,switch语句后面的表达式,与case语句后面的表示式比较运行结果时,采用的是严格相等运算符(===,而不是相等运算符(==),这意味着比较时不会发生类型转换。

2.1.6.4 表达式一定返回一个值——(https://www.zhihu.com/question/39420977/answer/81250170)

2.1.7.4 break语句用于跳出代码块或循环。continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。

如果存在多重循环,不带参数的break语句和continue语句都只针对最内层循环

break语句和continue语句怎么带参数:在 break / continue 后面添加一个 数字/标签 来表示跳出 几重循环/标签循环,eg:break 2;  /* 跳出2重循环*/;(http://wanlimm.com/77201406202191.html)

  (关于语句笔记的一个链接:https://www.jianshu.com/p/0ec3434f7781)

2.1.7.5 标签相当于定位符,可以是任意的标识符,通常配合break和continue使用。

对象文字是JavaScript的突出特点之一:它们允许您直接创建对象 - 不需要任何类。

  • 对象:对象将数据存储在属性中。每个属性都有一个名称和一个值。
     var obj = {
             propName1: 123,
             propName2: "abc"
         }
         obj.propName1 = 456;
         obj["propName1"] = 456; //与前面的语句相同
  • 阵列
        var arr = [true, "abc", 123];
        console.log(arr[1]); // abc
        console.log(arr.length); // 3

注意:函数和数组都是对象。例如,他们可以有属性:

    function foo() {}
    foo.bar = 123;

instanceof是一个二元操作符(运算符),它的作用是判断其左边对象是否为其右边类的实例,返回boolean类型的数据。

数据类型

2.2.2 JavaScript 有三种方法,可以确定一个值到底是什么类型。

  • typeof运算符(注意null返回object
  • instanceof运算符
  • Object.prototype.toString方法

2.2.3var 语句没有返回值。因为 var a = 1; 是声明语句,声明语句不返回任何东西,所以没有返回值。然后 a = 1; 是赋值语句,返回1。

2.2.4 如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true

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

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

2.3.4.1

/*javaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示法的符号位不同。它们是等价的。*/
-0 === +0 // true
0 === -0 // true
0 === +0 // true

/*唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的。*/
(1 / +0) === (1 / -0) // false
/*因为除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的*/

2.3.4.2 NaN表示“非数字”,并不是独立的数据类型,依然属于Number0除以0也会得到NaN

NaN的运算规则:

  • NaN不等于任何值,包括它本身。
    NaN === NaN // false
    
    /*数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。*/
    [NaN].indexOf(NaN) // -1
  • NaN在布尔运算时被当作false
    Boolean(NaN) // false
  • NaN与任何数(包括它自己)的运算,得到的都是NaN
    NaN + 32 // NaN
    NaN - 32 // NaN
    NaN * 32 // NaN
    NaN / 32 // NaN

2.3.4.3 Infinity表示“无穷”,有正负之分,正无穷(正的数值太大,eg:Math.pow(2, 1024))和负无穷(正的数值太大),另一种是非0数值除以0,也得到Infinity

Infinity === -Infinity // false

1 / -0 // -Infinity
-1 / -0 // Infinity

/*非零正数除以-0,会得到-Infinity,负数除以-0,会得到Infinity。*/

Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)。

InfinityNaN比较,总是返回false

Infinity > 1000 // true
-Infinity < -1000 // true

Infinity > NaN // false
-Infinity > NaN // false
Infinity < NaN // false
-Infinity < NaN // false

运算规则:

  • Infinity的四则运算,符合无穷的数学计算规则。
    5 * Infinity // Infinity
    5 - Infinity // -Infinity
    Infinity / 5 // Infinity
    5 / Infinity // 0

    0乘以Infinity,返回NaN;0除以Infinity,返回0Infinity除以0,返回Infinity

    0 * Infinity // NaN
    0 / Infinity // 0
    Infinity / 0 // Infinity

    Infinity加上或乘以Infinity,返回的还是Infinity

    Infinity + Infinity // Infinity
    Infinity * Infinity // Infinity

    Infinity减去或除以Infinity,得到NaN

    Infinity - Infinity // NaN
    Infinity / Infinity // NaN

    Infinitynull计算时,null会转成0,等同于与0的计算。

    null * Infinity // NaN
    null / Infinity // 0
    Infinity / null // Infinity

    Infinityundefined计算,返回的都是NaN

    undefined + Infinity // NaN
    undefined - Infinity // NaN
    undefined * Infinity // NaN
    undefined / Infinity // NaN
    Infinity / undefined // NaN

2.3.5.1 对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。

parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1

parseInt(0.0000008) // 8
// 等同于
parseInt('8e-7') // 8

parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。

parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512

如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0undefinednull,则直接忽略。

/*如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN。*/
parseInt('1546', 2) // 1
parseInt('546', 2) // NaN

如果parseInt的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。

parseInt(0x11, 36) // 43
parseInt(0x11, 2) // 1

// 等同于
parseInt(String(0x11), 36)
parseInt(String(0x11), 2)

// 等同于
parseInt('17', 36)
parseInt('17', 2)

这种处理方式,对于八进制的前缀0,尤其需要注意。

parseInt(011, 2) // NaN

// 等同于
parseInt(String(011), 2)

// 等同于
parseInt(String(9), 2)

 2.3.5.2 parseFloat会将空字符串转为NaN。这些特点使得parseFloat的转换结果不同于Number函数。

parseFloat(true)  // NaN
Number(true) // 1

parseFloat(null) // NaN
Number(null) // 0

parseFloat('') // NaN
Number('') // 0

parseFloat('123.45#') // 123.45
Number('123.45#') // NaN

2.3.5.3 isNaN方法可以用来判断一个值是否为NaN。判断NaN更可靠的方法是,利用NaN为唯一不等于自身的值的这个特点,进行判断。

function myIsNaN(value) {
  return value !== value;
}

2.3.5.4 isFinite方法返回一个布尔值,表示某个值是否为正常的数值。除了Infinity-InfinityNaN这三个值会返回falseisFinite对于其他的数值都会返回true

2.4.1.3 字符串可以被视为字符数组,但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。字符串内部的单个字符无法改变和增删。

2.4.1.4 length属性返回字符串的长度,该属性也是无法改变的。

2.4.3 Base64 转码

  • btoa():任意值转为 Base64 编码
  • atob():Base64 编码转为原来的值
    /*要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。*/
    function b64Encode(str) {
      return btoa(encodeURIComponent(str));
    }
    
    function b64Decode(str) {
      return decodeURIComponent(atob(str));
    }
    
    b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
    b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"

2.5.1.4  下面代码中,大括号就定义了一个对象,它被赋值给变量obj,所以变量obj就指向一个对象。该对象内部包含两个键值对(又称为两个“成员”),第一个键值对是foo: 'Hello',其中foo是“键名”(成员的名称),字符串Hello是“键值”(成员的值)。键名与键值之间用冒号分隔。第二个键值对是bar: 'World'bar是键名,World是键值。两个键值对之间用逗号分隔。

var obj = {
  foo: 'Hello',
  bar: 'World'
};

2.5.1.2 对象的所有键名都是字符串,加不加引号都可以。如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。

对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

var obj = {
  p: function (x) {
    return 2 * x;
  }
};

obj.p(1) // 2

如果属性的值还是一个对象,就形成了链式引用。

var o1 = {};
var o2 = { bar: 'hello' };
o1.foo = o2;
o1.foo.bar // "hello"
/*对象o1的属性foo指向对象o2,就可以链式引用o2的属性。*/

属性可以动态创建,不必在对象声明时就指定。

var obj = {};
obj.foo = 123;
obj.foo // 123

 2.5.1.3 如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

var o1 = {};"hello world"
var o2 = o1;

o1.a = 1;
o2.a // 1

o2.b = 2;
o1.b // 2
/*o1o2指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。*/

此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

var o1 = {};
var o2 = o1;

o1 = 1;
o2 // {}
/*o1和o2指向同一个对象,然后o1的值变为1,这时不会对o2产生影响,o2还是指向原来的那个对象*/

但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝

var x = 1;
var y = x;

x = 2;
y // 1
/*当x的值发生变化后,y的值并不变,这就表示y和x并不是指向同一个内存地址*/

2.5.1.4 如果行首是{ },即为语句/代码块,若要表示为表达式/对象,则要在大括号前加上圆括号。

/*这种差异在eval语句(作用是对字符串求值)中反映得最明显*/
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
/*如果没有圆括号,eval将其理解为一个代码块(foo理解为标签);加上圆括号以后,就理解成一个对象。*/

2.5.2.1 读取属性

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"
/*一种是使用点运算符,还有一种是使用方括号运算符读取属性p。*/

请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理

var foo = 'bar';

var obj = {
  foo: 1,
  bar: 2
};

obj.foo  // 1
obj[foo]  // 2
/*引用对象obj的foo属性时,如果使用点运算符,foo就是字符串;如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar。*/

方括号[ ]运算符内部还可以使用表达式。数字键可以不加引号,因为会自动转成字符串。

注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。

var obj = {
  123: 'hello world'
};

obj.123 // 报错
obj[123] // "hello world"
/*对数值键名123使用点运算符,结果报错。*/

2.5.2.2 点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

2.5.2.3 Object.keys查看一个对象本身的 所有 属性。

2.5.2.4 对象obj并没有p属性,但是delete命令照样返回true。需要注意的是,delete命令只能删除对象本身的属性,无法删除继承的属性

2.5.2.5 in运算符用于检查对象是否包含某个属性。in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。(例如toString方法不是对象obj自身的属性,而是继承的属性。)

2.2.2.6 for...in循环用来遍历一个对象的全部属性。

两个注意点:

  • 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
  • 它不仅遍历对象自身的属性,还遍历继承的属性。

对象都继承了toString属性,但是for...in循环不会遍历到这个属性。

ar obj = {};
// toString 属性是存在的
obj.toString // toString() { [native code] }

for (var p in obj) {
  console.log(p);
} // 没有任何输出

对象obj继承了toString属性,该属性不会被for...in循环遍历到,因为它默认是“不可遍历”的。

如果继承的属性是可遍历的,那么就会被for...in循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。

2.5.3 with语句的作用是操作同一个对象的多个属性,注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。所以必须先定义属性,再在with区块内操作它,因为with区块没有改变作用域,它的内部依然是当前作用域。这导致with语句绑定对象不明确。因此,建议不要使用with语句,可以考虑用一个临时变量代替with

with (对象) {
  语句;
}

var obj = {};
with (obj) {
  p1 = 4;  //等同于obj.p1=4
  p2 = 5;  //等同于obj.p2=5
}
obj.p1 // undefined
p1 // 4

with(obj1.obj2.obj3) {
  console.log(p1 + p2);
}
// 可以写成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);

 2.6.1 任何类型的数据,都可以放入数组。如果数组的元素还是数组,就形成了多维数组。

2.6.2 本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object。数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。(Object.keys方法返回数组的所有键名。)

由于数组成员的键名是固定的(默认总是0、1、2…),因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。

但是,对于数值的键名,不能使用点结构。

var arr = [1, 2, 3];
arr.0 // SyntaxError
/*arr.0的写法不合法,因为单独的数值不能作为标识符(identifier)。所以,数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。*/

2.6.3 length属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length设置的值。当length属性设为大于数组个数时,读取新增的位置都会返回undefined。清空数组的一个有效方法,就是将length属性设为0。值得注意的是,由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值。

var a = [];

a['p'] = 'abc';
a.length // 0

a[2.1] = 'abc';
a.length // 0

/*上面代码将数组的键分别设为字符串和小数,结果都不影响length属性。因为,length属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length属性保持为0。*/

2.6.4 检查某个键名是否存在的运算符in,适用于对象,也适用于数组。

2.6.5 for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。for...in不仅会遍历数组所有的数字键,还会遍历非数字键。

var a = [1, 2, 3];
a.foo = true;

for (var key in a) {
  console.log(key);
}
// 0
// 1
// 2
// foo
/*在遍历数组时,也遍历到了非整数键foo。因此不推荐使用for...in遍历数组。*/

使用for循环或while循环遍历数组。数组的forEach方法,也可以用来遍历数组。

var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
  console.log(color);
});
// red
// green
// blue

2.6.6 数组的空位是可以读取的,返回undefined,且其不影响length属性。使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性。

数组的某个位置是空位,与某个位置是undefined,是不一样的。如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。如果某个位置是undefined,遍历的时候就不会被跳过。

这就是说,空位就是数组没有这个元素,所以不会被遍历到,而undefined则表示数组有这个元素,值是undefined,所以遍历不会跳过。

2.6.7 “类似数组的对象”的根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组。但是有一个问题,这种length属性不是动态值,不会随着成员的变化而变化。

  1、数组的slice方法可以将“类似数组的对象”变成真正的数组。

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

  2、通过call()把数组的方法放到对象上面。

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);
/*arrayLike代表一个类似数组的对象,本来是不可以使用数组的forEach()方法的,但是通过call(),可以把forEach()嫁接到arrayLike上面调用。*/
/*在arguments对象上面调用forEach方法。*/
//
forEach 方法 function logArgs() { Array.prototype.forEach.call(arguments, function (elem, i) { console.log(i + '. ' + elem); }); } // 等同于 for 循环 function logArgs() { for (var i = 0; i < arguments.length; i++) { console.log(i + '. ' + arguments[i]); } }

字符串也是类似数组的对象,所以也可以用Array.prototype.forEach.call遍历。

不过,最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。

var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
  console.log(chr);
});
// a
// b
// c

 2.7.1.1 除了用function命令声明函数,还可以采用变量赋值的写法。

var f = function f() {};
/*函数名f只在函数体内部可用,指代函数表达式本身*/
//近似等价于 function f() {};

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

function f() {
  console.log(1);
}
f() // 2

function f() {
  console.log(2);
}
f() // 2
/*由于函数名的提升,前一次声明在任何时候都是无效的,这一点要特别注意。*/

2.7.1.3 函数可以调用自身,这就是递归(recursion)。

2.7.1.4 JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。

function add(x, y) {
  return x + y;
}

// 将函数赋值给一个变量
var operator = add;

// 将函数作为参数和返回值
function a(op){
  return op;
}
a(add)(1, 1)
// 2

2.7.1.5 如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。函数f提升到头部只能用function命令声明。

f();

function f() {}

2.7.1.6 因为函数f的提升所以不能在条件语句中声明函数。要达到在条件语句中定义函数的目的,只有使用函数表达式。

if (false) {
  var f = function () {};
}

f() // undefined

2.7.2.1 函数的name属性返回函数的名字。如果是通过变量赋值定义的函数,那么name属性返回变量名。如果变量的值是一个具名函数,那么name属性返回function关键字之后的那个函数名。

2.7.2.2 函数的length属性返回函数定义之中的参数个数。

2.7.2.3 利用toString方法变相实现多行字符串。

var multiline = function (fn) {
  var arr = fn.toString().split('
');
  return arr.slice(1, arr.length - 1).join('
');
};

function f() {/*
  这是一个
  多行注释
*/}

multiline(f);
// " 这是一个
//   多行注释"

 2.7.3.1 函数外部声明的变量就是全局变量,在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。函数内部定义的变量,会在该作用域内覆盖同名全局变量。注意,对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。

if (true) {
  var x = 5;
}
console.log(x);  // 5

2.7.3.2 与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

2.7.3.3 函数本身也是一个,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

var a = 1;
var x = function () {
  console.log(a);
};

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

f() // 1
/*函数x是在函数f的外部声明的,所以它的作用域绑定外层内部变量a不会到函数f体内取值,所以输出1,而不是2。*/

总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

2.7.4.2 Javascript 允许省略参数,省略的值变为undefined,并且函数和length属性与传入参数个数无关,只返回函数定义的参数,但是没法省略靠前的参数,若一定要省略,则传入undefined。

2.7.4.3 原始类型(数值、字符串、布尔值)的值传值传递,则在函数体内修改参数值不会影响到函数外部。复合类型的值(数组、对象、其他函数),传递方式是传址传递,则传入函数的原始值的地址会在内部修改,将会影响到原始值。注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。

var obj = [1, 2, 3];

function f(o) {
  o = [2, 3, 4];
}
f(obj);

obj // [1, 2, 3]
/*在函数f内部,参数对象obj被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)的值实际是参数obj的地址,重新对o赋值导致o指向另一个地址,保存在原地址上的值当然不受影响。*/

2.7.4.4 若函数有同名的参数,则取最后出现的那个值,调用函数时,没有提供第二个参数,则第二个参数的值默认为undefined,若要获得第一个的值,使用arguments对象。

2.7.4.5 (1).arguments对象包含了函数运行时的所有参数,在函数体内部使用,正常模式下,arguments对象可以在运行时修改,而在严格模式下('use strict'),修改是无效的。可以通达arguments对象的length属性判断函数调用时带了几个参数。(2).arguments对象不是数组,要让arguments对象使用数组方法,只有将其转变为真正的数组。

  1. slice方法和逐一填入新数组。
    var args = Array.prototype.slice.call(arguments);
    
    // 或者
    var args = [];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
  2. callee 属性,通过arguments.callee来调用函数本身,但在严格模式里禁用,因此不建议使用。

2.7.5.1 JavaScript 语言特有的”链式作用域”结构,子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

函数f2就是闭包,即能够读取其他函数内部变量的函数。 闭包最大的特点就是能够记住它的诞生环境,比如f2的诞生环境是f1。闭包可以读取函数内部变量并让这些变量始终保持在内存中,使内部变量记住上一次调用时的运算结果。

function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。因此可以看作闭包是函数内部作用域的一个接口。

闭包的另一个用处,是封装对象的私有属性和私有方法。

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
/*函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。*/

闭包内存消耗大,因此不能滥用,否则可能造成网页的性能问题。

2.7.5.2 在Javascript 中,圆括号()是一种运算符,函数() 表示调用该函数。

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

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

// 写法二
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());
/*写法二比写法一更好,因为完全避免了污染全局变量。*/

2.7.6 eval命令可以将字符串当作语句执行。不建议使用。eval()调用这种方式叫直接调用,作用域为当前作用域,除此之外,其他的调用方式都叫间接调用,作用域为全局作用域。与eval作用类似的还有Function构造函数。

2.8.1.1 加法运算符存在重载(加法运算符是在运行时决定,到底是执行相加,还是执行连接。运算子的不同,导致了不同的语法行为,这种现象称为“重载”)。并且算术运算符只有加法运算符进行重载。除加法外它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。

2.8.1.2 加法运算符如果运算子是对象,必须先转成原始类型的值,然后再相加。

2.8.2.1 余数运算符(%)运算结果的正负号由第一个运算子的正负号决定,所以为了得到负数的正确余数值,应先使用绝对值函数。

// 错误的写法
function isOdd(n) {
  return n % 2 === 1;
}
isOdd(-5) // false
isOdd(-4) // false

// 正确的写法
function isOdd(n) {
  return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false

2.8.2.4 指数运算符(**)。

 2.8.3 与位运算符的结合。

// 等同于 x = x >> y
x >>= y

// 等同于 x = x << y
x <<= y

// 等同于 x = x >>> y
x >>>= y

// 等同于 x = x & y
x &= y

// 等同于 x = x | y
x |= y

// 等同于 x = x ^ y
x ^= y

2.8.4 比较运算符返回布尔值,非相等的比较运算符先看两个运算子是否都是字符串,若是,则按Unicode 码(字典顺序)比较,若不是,转变为数值再进行比较。

2.8.4.1 字符串按照字典顺序比较。JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。所有字符都有 Unicode 码点,因此汉字也可以比较。

'cat' > 'Cat' // true'
/*小写的c的 Unicode 码点(99)大于大写的C的 Unicode 码点(67),所以返回true。*/

2.8.4.2 (1)两个原始类型的值的比较,除了相等运算符(==)和严格相等运算符(===),其他比较运算符都是先转成数值再比较。特殊情况:任何值包括NaN本身与NaN比较皆返回false。(2)如果运算子是对象,会转为原始类型的值,再进行比较。

[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'

[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'

{x: 2} >= {x: 1} // true
// 等同于 {x: 2}.valueOf().toString() >= {x: 1}.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'

注意,Date 对象实例用于比较时,是先调用toString方法。如果返回的不是原始类型的值,再接着对返回值调用valueOf方法。

2.8.4.3 注意NaN与任何值都不相等(包括自身)。另外,正0等于负0

两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。

{} === {} // false
[] === [] // false
(function () {} === function () {}) // false
/*上面代码分别比较两个空对象、两个空数组、两个空函数,结果都是不相等。原因是对于复合类型的值,严格相等运算比较的是,它们是否引用同一个内存地址,而运算符两边的空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是false。*/

注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。

undefinednull与自身严格相等。“严格不相等运算符”(!==),它的算法就是先求严格相等运算符的结果,然后返回相反值

2.8.4.4 对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。

[1] == 1 // true
// 等同于 Number([1]) == 1

[1] == '1' // true
// 等同于 String([1]) == Number('1')

[1] == true // true
// 等同于 Number([1]) == Number(true)

/*数组[1]与数值进行比较,会先转成数值,再进行比较;与字符串进行比较,会先转成字符串,再进行比较;与布尔值进行比较,两个运算子都会先转成数值,然后再进行比较。*/
  • undefinednull与其他类型的值比较时,结果都为false,它们互相比较时结果为true

相等运算符的缺点:

0 == ''             // true
0 == '0'            // true

2 == true           // false
2 == false          // false

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' 	
 ' == 0     // true
/*上面这些表达式都很容易出错,因此不要使用相等运算符(==),最好只使用严格相等运算符(===)。*

2.8.5.1 不管什么类型的值,经过取反运算后都变成了布尔值。

!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true

!54 // false
!'hello' // false
![] // false
!{} // false

两次取反就是将一个值转为布尔值的简便写法。

2.8.5.3 只通过第一个表达式的值,控制是否运行第二个表达式的机制,就称为“短路”(short-cut)。

2.8.5.4 三元条件表达式与if...else语句具有同样表达效果。但有一个重大差别if...else是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式

2.8.6 左移运算符(<<)表示将一个数的二进制值向左移动指定的位数,尾部补0,即乘以2的指定次方(最高位即符号位不参与移动)。右移运算符(>>)表示将一个数的二进制值向右移动指定的位数,头部补0,即除以2的指定次方(最高位即符号位不参与移动)。带符号位的右移运算符(>>>)表示将一个数的二进制形式向右移动,包括符号位也参与移动,头部补0。所以,该运算总是得到正值。对于正数,该运算的结果与右移运算符(>>)完全一致,区别主要在于负数。

2.8.7.1 void运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined

2.8.7.2 逗号运算符用于对两个表达式求值,并返回后一个表达式的值。

2.8.8.2 圆括号内只能放置表达式,如果将产品放在圆括号内就会报错。

2.9.1 强制转换主要指使用NumberStringBoolean三个函数,手动将各种类型的值,分布转换成数字、字符串或者布尔值。

  • (1) Number()
    // 字符串:如果不可以被解析为数值,返回 NaN
    Number('324abc') // NaN
    
    // 空字符串转为0
    Number('') // 0
    
    // 布尔值:true 转成 1,false 转成 0
    Number(true) // 1
    Number(false) // 0
    
    // undefined:转成 NaN
    Number(undefined) // NaN
    
    // null:转成0
    Number(null) // 0

    parseInt逐个解析字符,而Number函数整体转换字符串的类型。parseIntNumber函数都会自动过滤一个字符串前导和后缀的空格。

    parseInt('42 cats') // 42
    Number('42 cats') // NaN

    Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。

  • (2) String()
    • 数值:转为相应的字符串。
    • 字符串:转换后还是原来的值。
    • 布尔值true转为字符串"true"false转为字符串"false"
    • undefined:转为字符串"undefined"
    • null:转为字符串"null"
    String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
    String({a: 1}) // "[object Object]"
    String([1, 2, 3]) // "1,2,3"
    1. 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。

    2. 如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。

    3. 如果valueOf方法返回的是对象,就报错。

    String方法背后的转换规则,与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。
  • (3) Boolean()

    除了以下五个值的转换结果为false,其他的值全部为true

    • undefined
    • null
    • -0+0
    • NaN
    • ''(空字符串)
    注意,所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true。

2.10.2 Error对象及其派生错误都是构造函数。开发者可以使用它们,手动造成错误对象的实例。

2.10.4 遇上throw语句,就手动中断程序执行,抛出一个错误即任何类型的值。

2.10.5 不确定代码出错,则放入try...catch中,catch捕获错误后,不会中断程序,会按正常流程继续执行。且catch中还可以嵌套使用try...catch。

2.10.6 try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句。try...catch...finally这三者之间的执行顺序。

function f() {
  try {
    console.log(0);
    throw 'bug';
  } catch(e) {
    console.log(1);
    return true; // 这句原本会延迟到 finally 代码块结束再执行
    console.log(2); // 不会运行
  } finally {
    console.log(3);
    return false; // 这句会覆盖掉前面那句 return
    console.log(4); // 不会运行
  }

  console.log(5); // 不会运行
}

var result = f();
// 0
// 1
// 3

result
// false
/*catch代码块结束执行之前,会先执行finally代码块。catch代码块之中,触发转入finally代码快的标志,不仅有return语句,还有throw语句。*/
/*进入catch代码块之后,一遇到throw语句,就会去执行finally代码块*/
原文地址:https://www.cnblogs.com/CiMing/p/8544220.html