前端工作室专注前端开发
 博客首页

《JavaScript高级程序设计(第3版)》 1-4章总结

2020-10-14 16:36:05
JS基础与深入
39
文章图片

作为最经典最畅销的前端书籍之一,基本成为从事前端的必读书目,所以很有必要对该书进行必要的总结,以便日后更好的理解和运用。此篇为1-4章的总结归纳。

《JavaScript高级程序设计(第3版)》 (作者:Nicholas C.Zakas) 总结

第一章

1995 年布兰登·艾奇(Brendan Eich)开发出一款叫LiveScript 的脚本语言而后改名为JavaScript。一个完整的 JavaScript 实现应该由核心(ECMAScript)、文档对象模型(DOM)、浏览器对象模型(BOM)三个部分组成。所以我们可以使用核心语言功能去编写一些逻辑,可以用用文档对象模型(DOM)提供的方法触发某些逻辑,这些逻辑也可以改变文档对象模型(DOM)某些DOM属性,进而可以人机交互,同时浏览器对象模型(BOM)使你可以提供某些浏览器信息和操作。

第二章 在 HTML 中使用 JavaScript

2.1<scripct>元素

<script>元素的属性 1、async: 可选,表示应该立即下载脚本,但不应妨碍页面中的其他操作。必须有src属性才生效。 2、defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。必须有src属性才生效。 3、src:可选。指定js文件路径。 4、type:可选。默认值text/javascript 如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)。 如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行,一般是为了获取某些dom元素,也可以把代码写在window.onload解决,经我试验得知,window.onload甚至晚于defer执行,多个defer按顺序执行,defer与无defer,defer最后执行。 如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本,如果<script>放在末尾,相当于使用defer

做个简单测试,眼见为实

<script src="a.js"></script>
<script src="b.js"  async></script>
<script src="c.js"></script>
<script src="d.js"  defer></script>
<script src="e.js"  async></script>
<script src="f.js"  defer></script>
<script src="g.js"></script>

输出结果为a、b、c、e、g、d、f,下载顺序为a,b,c,d,e,f 当b.js很小的时候感觉上有async和无async无defer的执行顺序是按出现顺序,但是当我加大b.js体积时,输出结果为a、c、g、e、d、b、f,当我无论如何加大g.js,执行顺序都没有发生改变,所以实际上有asyn属性时,文件什么时候下载完什么时候执行,两个<script>同样具有该属性时,两者执行顺序没有任何关系;而有defer属性的最后执行,但是也不会等待有async属性的执行后才最后执行;唯一能确定的是有defer属性的比无async无defer属性的晚执行。这两个属性的出现是为了解决阻塞页面解析的问题

2.11标签的位置

使用<script>元素的方式有两种:直接在页面中嵌入 JavaScript 代码和包含外部 JavaScript 文件。

当浏览器遇到字符串""时,就会认为那是结束的标签。以下代码会发生错误:

<script type="text/javascript">
    function test(){
        console.log("</script>");
    }
</script>

需要经过转义符

<script type="text/javascript">
    function test(){
        console.log("<\/script>");
    }
</script>

2.2 嵌入代码与外部文件

最好的方法是通过外部文件来引入代码,具有可维护性、可缓存、适应未来等特点。

2.3 文档模式

两种文档模式:混杂模式(quirks mode)和标准模式(standards mode)。未声明,则所有浏览器都会默认开启混杂模式。但是并不提倡使用混杂模式,所以一般指定某种标准模式,目前通常使用的是在文档开头添加

2.4 <noscript>元素

用以在不支持 JavaScript 的浏览器中显示替代的内容,和html5中的<video>类似。

第三章 基 本 概 念

3.1 语法

1、区分大小写 2、标识符:第一个字符必须是一个字母、下划线(_)或一个美元符号($);其他字符可以是字母、下划线、美元符号或数字 3、注释

// 单行注释

/*
* 这是一个多行
* 注释
*/

3、use strict 使用严格模式 4、分号结尾,代码块被{}包裹

3.2 关键字、保留字

3.3 变量

ECMAScript 的变量是松散类型的,有全局变量和局部变量之分,全局变量可在任何地方访问,而局部变量无法在全局环境下访问。

function test(){
var message = "hi"; // 局部变量
}
test();
alert(message); // 错误

值得一提的是,在非严格模式下,

function test(){
    message = "hi"; // 全局变量
}
test();
alert(message); // "hi"

代码块中未声明变量变为了全局变量,可以在函数外部访问。 另外,你还可以随意赋给变量任何类型的值,虽然不提倡,例如

var message = "hi";
message = 100; // 有效,但不推荐

这就是js为人所诟病的一点,所以现在出现了TS,可以定制变量类型, 例如

let decLiteral: number = 6;

不过浏览器目前无法直接支持ts文件。 一条语句可以定义多个变量,因为ECMAScript 的变量是松散类型的原因 例如

var message = "hi",
    found = false,
    age = 29;

3.4 数据类型

6 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和 String、Symbol(新增),1种复杂数据类型——Object。

3.4.1 typeof操作符

使用typeof检测数据类型 眼见为实:

var a = undefined, 
    b = true, 
    c = 'c',
    d = 1,
    e = {},
    f = null,
    g =  function(){},
    h=[],
    i= new String(),
    j= new Number(),
    k= new Boolean(),
    l= /lzw/,
    m= String('hello');
console.log(typeof a) // undefined
console.log(typeof b) // boolean
console.log(typeof c)// string
console.log(typeof d)// number
console.log(typeof e)// object
console.log(typeof f)// object
console.log(typeof g)// function
console.log(typeof h)// object
console.log(typeof i)// object
console.log(typeof j)// object
console.log(typeof k)// object
console.log(typeof l)// object
console.log(typeof m)// string

输出结果:undefined,boolean,string,number,object,object,function,object,object,object,object,object,string 值得注意的是: 1、typeof null 返回object(js中的一个bug),甚至某些旧浏览器中返回function 2、String,Number,Boolean在JS中是基本类型,基本类型是存储在栈(stack)内存中的,数据大小确定,内存空间大小可以分配。String()、Number()、Boolean()定义出来的是在栈中并且值相等,而new String()、new Number()、new Boolean()方法定义出来的仅仅是栈中的一个指针。 3、会返回object的有null、数组、正则、new操作符等。这简直就是灾难。

3.4.2 Undefined类型

var message;
console.log(message)// 输出undefined
console.log(typeof age) // 输出undefined
console.log(age)// Uncaught ReferenceError: 报错age is not defined

值得注意的是:未声明变量使用typeof操作符返回undefined,但是并不报错,另外对于未传递参数的参数值为undefined

3.4.3 Null类型

空指针,所以typeof null 返回object 值得注意的是:

console.log(null == undefined) // true

undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true

3.4.4 Boolean类型

眼见为实:

console.log(Boolean(true)) // true
console.log(Boolean(false)) // false
console.log(Boolean('')) // false
console.log(Boolean('0')) // true
console.log(Boolean(0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean(null)) // false
console.log(Boolean({})) // true
console.log(Boolean(undefined)) // false

简而言之,false、空字符串''、0和NaN、null、undefined会被转换为false

3.4.5 Number类型

八进制(注意:严格模式下无效) var octalNum1 = 070; // 八进制的 56 var octalNum2 = 079; // 无效的八进制数值——解析为 79 var octalNum3 = 08; // 无效的八进制数值——解析为 8

在严格模式之中,该方法无效,ES6 进一步明确,要使用前缀0o表示。 所以推荐使用

0o767 //十进制数值为503

十六进制 var hexNum1 = 0xA; // 十六进制的 10 var hexNum2 = 0x1f; // 十六进制的 31 1、浮点数 var floatNum1 = 1.2; var floatNum2 = 0.2; var floatNum3 = .2; // 有效,但不推荐 注意为了节约存储空间,必要时会转换为整数 var floatNum1 = 2.; // 转变为 2 var floatNum2 = 20.0; // 转变为 20 浮点数值配合e表示科学计数法 var floatNum = 3.111e7; // 31110000 var floatNum = 3e-17; // 0.00000000000000003 特别注意

0.1 + 0.2 == 0.3 // false
0.1+ 0.2 == 0.30000000000000004 // true

因为总会不可避免的出现相加数值比较的问题,所以如何避免这种情况呢 ES6解决方法:

function numbersequal(a,b){ 
    return Math.abs(a-b)<Number.EPSILON; 
} 
var a=0.1+0.2, 
    b=0.3; 
console.log(numbersequal(a,b)); //true

其他方法:

(0.1*10+0.2*10)/10 ===0.3
parseFloat((0.1+0.2).toFixed(10)) ===0.3

2、数值范围 超过数值范围,如果是正数的话,转换成 Infinity(正无穷),如果是负数的话,转换成 -Infinity(负无穷) 可以使用 isFinite()函数判断数值是否有效

var result = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result)); //false

3、NaN NaN,即非数值(Not a Number)是一个特殊的数值,避免抛出错误。 注意一下几种情况

NaN == NaN // false
0/0 // NaN
1/0 // Infinity
-1/0 // -Infinity

所以无法通过==判断是否为NaN isNaN()函数

alert(isNaN(NaN)); //true
alert(isNaN(10)); //false(10 是一个数值)
alert(isNaN("10")); //false(可以被转换成数值 10)
alert(isNaN("blue")); //true(不能转换成数值)
alert(isNaN(true)); //false(可以被转换成数值 1)

4、数值转换 有 3 个函数可以把非数值转换为数值:Number()、parseInt()和 parseFloat()。 4.1 Number()函数的转换规则如下。

  • 布尔值false和true分别转换为0和1
  • 数值简单传入和传出
  • null转换为0
  • undefined转换为NaN
  • 字符串规则如下
    • 只包含数字(包括正负号),转换为十进制,回忽略前导零
    • 有效的浮点格式,如'1.1'有效,'1.1.1'无效
    • 空字符串返回0
    • 其他字符串返回NaN
  • 如果是对象先调用valueOf()方法,返回NaN的话再调用toString()方法。

眼见为实

Number(true) // 1
Number(false) // 0
Number(2) // 2
Number(null) // 0
Number(undefined) // NaN
Number('123') // 123
Number('0123') // 123(忽略前导零)
Number('1.23') // 1.23
Number('01.23') // 1.23(忽略前导零)
Number(0xf) // 15
Number('') // 0
Number({a: 1}) // NaN
Number({}) // NaN
Number({valueOf: function(){
    return '1'
} }) // 1
Number({toString: function(){ console.log('1');return 1}}); // 1
Number({valueOf: function(){return 'lzw'},toString: function(){ return 1}}); // NaN(无valueOf函数,toStriing函数才能执行)
Number(new String('1')) // 1 (调用的就是valueOf()函数)
Number('123lzw') // NaN

4.2 parseInt()函数规则 1、忽略字符串前面空格 2、第一个字符必须为负号或者数字,否则返回NaN 3、如果第一个字符是数字会一直解析知道遇到非数字字符,包括小数点 4、能够识别各种整数格式

parseInt('   123') // 123
parseInt('') // NaN
parseInt('   123web') // 123
parseInt('123.123') // 123
parseInt(123.123) // 123
parseInt('0xf') // 15(十六进制)
parseInt(0xf) // 15
parseInt("070") // 70(测试结果与书上不同,因为书上记录的是ECMAScript 3运行结果56,ECMAScript 5 认为是 70)
parseInt(070) // 63
parseInt('0o77') // 0(0o开头ES6八进制表示方式)
parseInt(0o77) // 63(0o开头ES6八进制表示方式)
parseInt('0b111') // 0
parseInt(0b111) // 7(二进制)

涉及到进制问题,转换字符串时,只有十六进制能够转换成功,二进制和八进制会存在问题。 parseInt()函数的第二个参数

var num = parseInt("0xAF", 16); //175
var num = parseInt("AF", 16); //175(可以出掉0x)
parseInt('0o77',8) // 0
parseInt('77',8) // 63
parseInt('0b111',2) // 0
parseInt('111',2) // 7

转换字符串对于八进制和二进制加了第二个参数依然无效,除非除去'0b'和'0o'

var num1 = parseInt("10", 2); //2 (按二进制解析)
var num2 = parseInt("10", 8); //8 (按八进制解析)
var num3 = parseInt("10", 10); //10 (按十进制解析)
var num4 = parseInt("10", 16); //16 (按十六进制解析)

所以必须指定第二个参数,否则可能转换为十六进制

3.4.6 String类型

String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,即字符串。

var name = 'lzw' //使用单引号
var user = "lzw" // 使用双引号

1、字符字面量 字符字面量将被当作一个字符来解析,只占一个字符长度。 例如:

console.log('He said, \'hey.\'') //输出 He said, 'hey.'
console.log("He said, \"hey.\"") //输出 He said, "hey."
console.log('www\nss') // 输出
// www
// ss
console.log('\\') // 输出\
console.log('\x41') // 输出A
console.log('\u03a3') // 输出Σ

2、字符串的特点 ECMAScript 中的字符串是不可变的。所以对字符串重新赋值时,都是先销毁填充。 3. 转换为字符串 方法一: 使用toString()

(11).toString() // 输出 11
(true).toString() // 输出 'true'
(null).toString() // 报错
(undefined).toString() // 报错

但 null 和 undefined 值没有这个方法。 另外需要特别注意的是,数值调用toString()方法时,可以传递一个参数,可以输出其他进制表示的字符串

var num = 10; 
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"

转型函数 String()可以解决null和undefined无法转换的问题

console.log(String(10)) // '10'
console.log(String(true)) // 'true'
console.log(String(null)) // 'null'
console.log(String(undefined)) // 'undefined'

3.4.7 Object类型

var o = new Object(); 对象属性和方法 1、constructor :保存创建对象的函数

var o = new Object();
console.log( o.constructor) // 输出 ƒ Object() { [native code] }
function a(){
    this.a=1
};
var o = new a();
console.log(o.constructor) // 输出ƒ a(){this.a=1}

2、hasOwnProperty(propertyName): 判断属性存在当前对象实例中而非实例原型中。

function person() {
    this.name='lzw'
};
person.prototype = {age: 18};  // 构造函数的prototype指向了一个对象,而这个对象正是调用构造函数时创建的实例的原型
var Person = new person();
console.log(Person.name,Person.age) // 输出 'lzw' 18
console.log(Person.hasOwnProperty("name")) // true(存在于实例)
console.log(Person.hasOwnProperty("age")) //false(存在于原型)

3、isPrototypeOf(object): 用于检查是否是传入对象的原型

function person() {
    this.name='lzw'
};
var age= {age: 18};
person.prototype = age; 
var Person = new person();
console.log(Person.isPrototypeOf(age)); // false
console.log(age.isPrototypeOf(Person)); // true(证明age是Person的原型)

4、propertyIsEnumerable(propertyName): 能否被for in 枚举

var o = {a: 1};
console.log(o.propertyIsEnumerable('a'));// true(a属性可以被遍历)
for(property in o){console.log(property)}// a (的确遍历了出来)

5、toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。

var o = {a: 1};
console.log(o.toLocaleString()); // 输出[object Object]
var num = new Number(11);
console.log(num.toLocaleString()) // “11”

6、toString():返回对象的字符串表示。

var o = {a: 1};
console.log(o.toString()); // 输出[object Object]
var num = new Number(11);
console.log(num.toString()) // "11"

7、valueOf():返回对象的字符串、数值或布尔值表示。

var o = {a: 1};
console.log(o.valueOf()); // 输出 {a: 1}
var num = new Number(11);
console.log(num.valueOf()) // 11

3.5 操作符

操作符用于操作数据值,有算术操作符(如加号和减号)、位操作符、关系操作符和相等操作符。

一元操作符

只能操作一个值的操作符叫做一元操作符 1、递增递减操作符

var num1 = 10;console.log(num1--,num1); // 10,9
var num2 = 10;console.log(--num2,num2); // 9,9
var num3 = 10;console.log(num3++,num3); // 10,11
var num4 = 10;console.log(++num4,num4); // 11,11

递增和递减操作符遵循下列规则

  • 字符串先转换为数字再加减一
  • 布尔值false,转换为0再加减1
  • 布尔值true, 转换为1再加减1
  • 浮点数加减1
  • 对象调用valueOf()方法
var a="1";console.log(a++,a) // 输出 1,2
var a1="1lzw";console.log(a++,a) // NaN,NaN
var b = false;console.log(b++,b) // 0,1
var b1 = true;console.log(b1++,b1) // 1,2
var c= 1.1;console.log(c--,c) // 1.1 0.10000000000000009(浮点舍入错误所致)
var obj={
    valueOf: function(){return '1'}
 };
 console.log(obj--,obj) // 1 0

再次提醒,js的浮点数运算相当危险,特别是判断运算结果两边是否相等时 2. 一元加和减操作符 一元加操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响,非数值类型时,转换规则和Number()一样

  • 布尔值false和true分别转换为0和1
  • null转换为0
  • undefined转换为NaN
  • 字符串规则如下
    • 只包含数字(包括正负号),转换为十进制,回忽略前导零
    • 有效的浮点格式,如'1.1'有效,'1.1.1'无效
    • 空字符串返回0
    • 其他字符串返回NaN
  • 如果是对象先调用valueOf()方法,返回NaN的话再调用toString()方法

眼见为实,尝试一下:

var a = 1; console.log(+a,a) // 1,1
var b = false;console.log(+b) // 0
var c = true;console.log(+c) // 1
var d = null;console.log(+d) // 0
var e = undefined;console.log(+e) // NaN
var f = '01';console.log(+f); // 1
var g = '01.1';console.log(+g); // 1.1
var h = '01.1.1';console.log(+h); // NaN
var i = '';console.log(+i); // 0
var j = '123lzw';console.log(+j); // NaN
var k = {
    valueOf: function() {
        return 1;
    }
};
console.log(+k); // 1

可用于隐形转换数据类型。

3.5.2 位操作符

1、按位非

var num1 = 25;
var num2 = ~num1; 
console.log(num2) // -26

2、 按位与

var result = 25 & 3;
console.log(result); //1

3、按位或

var result = 25 | 3;
console.log(result); //27

4、按位异或

var result = 25 ^ 3;
console.log(result); //26

其他略

3.5.3 布尔操作符

1、逻辑非 逻辑非操作符叹号!。 规则如下:

  • 操作数为对象,返回false
  • 操作数为字符串,空字符串返回,true,其他返回false
  • 操作数为数字,0,返回true,其他返回false
  • 操作数为null,NaN,undefined返回true

眼见为实:

console.log(!{}) // false
console.log(!'') // true
console.log(!'lzw') // false
console.log(!0) // true
console.log(!1) // false
console.log(!null,!NaN,!undefined) // true true true

!!相当于使用Boolean(),即隐式转换为布尔值 2、逻辑与 逻辑与操作符&&

console.log(1 && 2) // 2
console.log(0 && 2) // 0
console.log(NaN && 1) // NaN
console.log(null && 1) // null
console.log(undefined && 1) // undefined

其结果并不是true或者false,而是两个操作数里面的一个,具有短路效应 常用来判断属性存在或者dom节点存在再执行某些操作,不存在时直接被短路。 3、逻辑或 逻辑或使用||

console.log(1 || 2) // 1
console.log(0 || 2) // 2
console.log(0 || NaN) // NaN
console.log(0 || null) // null
console.log(0 || undefined) // undefined

其结果并不是true或者false,而是两个操作数里面的一个,不具有短路效应

3.5.4 乘性操作符

1、乘法* 2、除法/ 3、求模%

3.5.5 加性操作符

1、加法+ 注意:字符串和数字相加会变为字符串拼接

console.log(1+'1') // '11'(拼接字符串)
console.log(1+null) // 1null转换为0console.log(1+NaN) //NaN
console.log(1+undefined) // NaN
console.log(1+{}) // 1[object Object](调用toString()方法)

只有null可以被转化为0,还是挺灾难的 2、减法

console.log(1-'1') // 0 ('1'转换为1)
console.log(1-NaN) // NaN
console.log(null-1) // -1
console.log(undefined-1) // NaN
console.log(1-{}) //NaN

只有null可以被转化为0,还是挺灾难的

3.5.6 关系操作符

小于(<)、大于(>)、小于等于(<=)和大于等于(>=) 规则如下

  • 数值直接比较数值
  • 字符串逐个比较字符编码值
  • 其他一律转换为数值再比较,字符串会转会为NaN或者数值,对象依然调用valueOf()或者toString()
var a = 1,b =2,c =' a',
    d = {
        valueOf:function(){
            return '2'
        }
    },
    e = true,
    f ='b',
    g = '2';
console.log(a>b) // false
console.log(a<c) // false(数值和字符串比较返回false,因为字符串转换为NaN)
console.log(a>c) // false(同上)
console.log(a<d) // true(对象调用valueOf())
console.log(a<e) // true(布尔值true转换为1)
console.log(c<f) // true(对比字符串编码)
console.log(a<g) // true('2'转换为2)

3.5.7 相等操作符

相等==和不相等!=: 转换后再比较 相等===和不相等!==: 直接比较,所以会比较数据类型 1、相等==和不相等!= 规则如下:

  • 布尔值true转换为1,false转换为0
  • 数值和字符串比较,字符串转换为数值或者NaN
  • 只有其中一个为对象,依然调用valueOf()方法
  • null == undefined, 但是这两者都没有转换为0,因为 null == 0 返回false,undefined == 0 返回false
  • NaN == NaN返回false,NaN与任何数据类型进行相等比较都返回false,不相等操作符返回true,包括NaN自身
  • 两个都是对象,比较指向的内存地址。

又是眼见为实系列:

console.log(1 == true) // true(true转换为1)
console.log(0 == false) // true(false转换为0)
console.log(1 == '1') //true('1'转换为1)
console.log(1=='1lzw') //false('1lzw'转换为NaN)
console.log(1=={valueOf:function(){return 1}}) // true(对象调用valueOf()方法)
console.log(null == undefined) // true(没有理由就是相等)
console.log(null == 0// false(null 不转换为0)
console.log(undefined == 0// false(undefined 不转换为0)
console.log(NaN == NaN) // false(NaN自身不相等)
console.log(NaN != NaN) // true(NaN自身不相等)
console.log(null==null) // true
console.log(undefined==undefined) // true
var a = {};var b = a; var c = a; c.name = 'lzw';console.log(b == c) // true(指向相同的内存地址,改变c的时候实际上a,b都跟着改变了)

null和undefined不会转换为0但是两者又相等,这真的是灾难,奇怪的知识又增长了 2、全等和不全等 眼见为实

console.log(1 == '1') // true(转换后相等)
console.log(1 === '1') // false(数据类型不相等)
console.log(null == undefined) // true(没有理由就是相等)
console.log(null === undefined) // false(数据类型不相等)

3.5.8 条件操作符

variable = boolean_expression ? true_value : false_value; 根据布尔表达式的值返回不同的结果,例如

result = true ? 1: 2;console.log(result) // 1
result = false ? 1: 2;console.log(result) // 2

3.5.9 赋值操作符

var value = 1; value = vlaue + 1; 可以简化为 var value = 1; value += 1; 同理还有

  • 乘/赋值(*=);
  • 除/赋值(/=);
  • 模/赋值(%=);
  • 加/赋值(+=);
  • 减/赋值(-=);
  • 左移/赋值(<<=);
  • 有符号右移/赋值(>>=);
  • 无符号右移/赋值(>>>=)。

这些操作符不会带来性能上的提升。

3.5.10 逗号操作符

var a =1, b=2, c=3 使用逗号操作符进行多个变量声明操作。 var a = (1, 2, 3, 4, 0); // a 的值为 0 奇怪的知识又增长了

3.6 语句

3.6.6 if语句

if (condition) statement1 else statement2

if(true){
    console.log('条件正确执行这里')
}else{
    console.log('条件不正确执行这里')
}
// 输出 '条件正确执行这里'
if(false){
    console.log('条件正确执行这里')
}else{
    console.log('条件不正确执行这里')
}
// 输出 '条件不正确执行这里'

还有依次判断条件是否正确的情况 if (condition1) statement1 else if (condition2) statement2 else statement3

3.6.2 do-while语句

do { statement } while (expression); 循环体内代码至少执行一次,而后需要满足条件才能执行

var i = 0;
do {
    i++;
    console.log(i)
} while(i < 10) 
console.log(i)
// 输出 1 2 3 4 5 6 7 8 9 10 10

i递增到10的时候已经不满足条件

3.6.3 while语句

while(expression) statement 满足条件时再执行

var i = 0
while( i < 10 ){
    i++
    console.log(i)
}// 输出 1 2 3 4 5 6 7 8 9 10

3.6.4 for语句

for (initialization; expression; post-loop-expression) statement

for(var i= 0; i< 10;i++){
    console.log(i)
} // 输出 0 1 2 3 4 5 6 7 8 9
console.log(i) // 输出 10

无限循环

for (;;) { // 无限循环
    doSomething();
}

一下写法相当于while

for( var i = 0; i < 10; ){
     i++;
     console.log(i)
}

3.6.5 for-in语句

for (property in expression) statement

var o={a:1};
for(var key in o){
    console.log(key,o[key])
}// 输出 a 1

注意对象属性是没有顺序的,所以for-in输出也是没法确定顺序的

3.6.6 label语句

label: statement

start: for (var i=0; i < 10; i++) {
    alert(i);
}

3.6.7 break和continue语句

for(var i= 1;i< 10;i++){
    if(i%3 === 0){
        console.log(i +'的时候跳出整个循环');
        break; // 跳出整个循环
    }
    console.log(i)
} // 输出1 2 3的时候跳出整个循环'
for(var i= 1;i< 10;i++){
    if(i%3 === 0){
        console.log(i + '的时候跳出内循环');
        continue; // 跳出内循环
    }
    console.log(i)
} // 输出1 2 '3的时候跳出内循环' 4 5 '6的时候跳出内循环' 7 8 '9的时候跳出内循环'

break结合label使用,break后面带着需要返回的循环

outerLabel : for( var i = 0; i < 4; i++){
    for(var j = 0; j < 4; j++){
        if( i == 2&& j == 2){
             break outerLabel
        }
        console.log(i,j)
    }
} // 输出 
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// 1 3
// 2 0
// 2 1

直接整个循环跳出不再执行

for( var i = 0; i < 4; i++){
    outerLabel : for(var j = 0; j < 4; j++){
        if( i == 2&& j == 2){
             break outerLabel
        }
        console.log(i,j)
    }
}// 输出
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// 1 3
// 2 0
// 2 1
// 3 0
// 3 1
// 3 2
// 3 3

跳过了'2 2'、'2 3'的输出,跳出内循环,把控制权交给外循环,此时i==2,i+1后继续循环

下面看看continue和label结合使用的情况

outerLabel : for( var i = 0; i < 4; i++){
    for(var j = 0; j < 4; j++){
        if( i == 2&& j == 2){
             continue outerLabel
        }
        console.log(i,j)
    }
}  
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// 1 3
// 2 0
// 2 1
// 3 0
// 3 1
// 3 2
// 3 3
for( var i = 0; i < 4; i++){
    outerLabel : for(var j = 0; j < 4; j++){
        if( i == 2&& j == 2){
             continue outerLabel
        }
        console.log(i,j)
    }
}
// 输出
// 0 0
// 0 1
// 0 2
// 0 3
// 1 0
// 1 1
// 1 2
// 1 3
// 2 0
// 2 1
// 2 3
// 3 0
// 3 1
// 3 2
// 3 3

前一个跳过了'2 2'、'2 3',因为跳出到i==2的位置,i+1后继续循环;后一个跳过了'2 2',因为跳出到j==2的位置,j+1后继续循环。细微的差别还需要仔细观察,主要是跳的位置不一样,输出结果也大为不同容易混淆。 不建议过度使用,否则会把自己绕晕过去

3.6.8 with 语句

with (expression) statement; with 语句的作用是将代码的作用域设置到一个特定的对象中。

var o = {
    a: 1,
    b: 2
};
with(o){ 
    a = a+ 1;
    b = b+1
};
console.log(o) // 输出 {a: 2, b: 3}

严格模式下不允许使用with语句;而且使用该语句会导致性能下降。总而言之,不要用它。

"use strict"
var o = {a: 1, b:2};with(o){ a = a+ 1;b=b+1};
console.log(o) //报错 Uncaught SyntaxError: Strict mode code may not include a with statement

3.6.9 switch 语句

switch (expression) { case value: statement break; case value: statement break; case value: statement break; case value: statement break; default: statement } 实际上就是if-else的一种等同语句。注意:要用break或者return 跳出执行流语句。

var i = 4;
switch (i){
    case 1: console.log(1);break;
    case 2: console.log(2);break;
    case 3: console.log(3);break;
    default: console.log(4) 
} //输出 4
var i = 2;
switch (i){
    case 1: console.log(1);
    case 2: console.log(2);
    case 3: console.log(3);
    default: console.log(4) 
} // 输出 2 3 4
var i = 2;
switch (i){
    case 1: console.log(1);break;
    case 2: 
    case 3: console.log('2或者3');break; //合并两个条件
    default: console.log(4) 
}
var i = '';
console.log(0 == '') // 输出 true
switch (i){
    case 0: console.log(0);break;
    case 1: console.log(1);break;
    case 2: 
    case 3: console.log('2或者3');break;
    default: console.log(4) 
} // 输出 4(所以switch的比较是全等比较)

记得加上break;否则后面的语句依然会按顺序执行。同时可以合并两个条件。另外switch的比较是全等比较

function a(){
    var i = 4;
    switch (i){
        case 1: console.log(1);return 1;
        case 2: console.log(2);return 2;
        case 3: console.log(3);return 3;
        default: console.log(4) 
    }
}
console.log(a()) // return 直接不再执行函数,所以在函数中可以使用switch加return组合,不在函数中使用会报错

3.7 函数

使用function声明,封装一段代码,一遍于其他地方调用。 function functionName(arg0, arg1,...,argN) { statements }

function howToUse(){
    console.log("我调用了函数了")
}
howToUse(); // 输出 '我调用了函数了'
function add(a,b){
    return a + b; // 遇到return,本行代码结果会返回,后面代码将不再执行
    console.log(a+b) // 永远不会被执行
}
add(1, 1) // 输出 2

注意: return 以后会停止并退出 严格模式下:

  • 函数名不能为eval 或 arguments
  • 参数名不能为eval 或 arguments
  • 参数名不能一样

眼见为实:

"use strict" 
function eval(){}; // 报错 Uncaught SyntaxError: Unexpected eval or arguments in strict mode
"use strict" 
function arguments(){}; // 报错 Uncaught SyntaxError: Unexpected eval or arguments in strict mode
"use strict" 
function a(eval){}; // 报错 Uncaught SyntaxError: Unexpected eval or arguments in strict mode
"use strict" 
function a(arguments){}; // 报错 Uncaught SyntaxError: Unexpected eval or arguments in strict mode
"use strict" 
function a(b,c,b){}; // 报错 Uncaught SyntaxError: Duplicate parameter name not allowed in this context

3.7.1 理解参数

参数类型不需要指定,参数即使不传也不会有问题(js又一个被吐槽的点:未指定参数类型,也不指定函数返回的类型)。参数在内部是用一个数组来表示,可以通过函数体内的arguments对象来访问。arguments是一个类数组对象,并不是Array实例,但是可以通过arguments[0]来访问传参。

function a(b,c,d){
    console.log(Array.isArray(arguments))
};
a(); //输出 false

甚至你可以隐式的使用这些参数

function a(){
    for(var i = 0; i < arguments.length; i++){
        console.log(arguments[i])
    }
}
a(1,2,3,4,5); // 输出 1 2 3 4 5
function a(b,c){
    arguments[0] = 0;
    console.log(b)
};
a(1,2); // 输出 0

没有定义这些参数,也可以获得这些参数的值。你甚至可以通过arguments修改传入得参数值

function a(b,c){
    console.log(b,c)
};
a(); // 输出 undefined undefined

注意:没有传递值参数会自动赋予undefined

严格模式下:

  • 对arguments赋值无效
  • 重写arguments会导致语法错误

眼见为实

"use strict"
function a(b,c){
    arguments[0] = 0;
    console.log(b)
};
a(1,2); // 输出 1

无法修改传参

"use strict"
function a(b,c){
    arguments = {0: 1,length: 1};
    console.log(b)
};
a(1,2); // 报错 Uncaught SyntaxError: Unexpected eval or arguments in strict mode

无法重写arguments

3.7.2 没有重载

后定义的函数会覆盖前面定义的函数

第四章 变量、作用域和内存问题

4.1基础类型和引用类型

基本类型值:简单数据段(基本值数据类型:Undefined、Null、Boolean、Number和String,变量是存放在栈区的) 引用类型值:多个值构成的对象(按引用访问,栈区内存保存变量标识符和指向堆内存中该对象的指针)

4.1.1 动态的属性

var person = new Object();
person.age = 18;
console.log(person.age) // 输出 18
var str = new String('lzw');
str.age = '18';
console.log(str.age) // 输出 18

引用类型的值,可以增加删除改变其属性和方法 基本类型的值添加属性无效

var a= 1;
a.age = 18;
console.log(a.age) // 输出 undefined

4.1.2 复制变量值

基本类型的值:把一个基本类型的值从一个变量赋值复制到另一个变量,那么两个变量都会拥有这个值。如

var a = 100;
var b = a;

引用类型的值:把一个引用类型的值,从一个变量赋值复制到另一个变量,那么两个变量都会拥有指向同一个对象的指针。所以改变其中一个对象另一个对象也会随之改变。

var o ={a: 0}; 
var b = o; 
var c = b; 
b.a1 = 1;  // 对b操作实际操作的是{a: 0},相当于o.a1 = 1
c.a2 = 2; // 对c操作实际操作的是{a: 0},相当于o.a2 = 1
console.log(o) // 输出 {a: 0, a1: 1, a2: 2}

复制后无论对那个操作都会直接操作堆内存内的对象,进而影响所有存有该对象指针的变量。 这会给我们编写代码的时候造成的最直接麻烦就是:无法通过赋值方式获得一个拥有新的内存地址的对象,几个变量之间藕断丝连,有时候程序员就是只想改变其中之一的变量而已。 以后会介绍如何获取一个拥有新的内存地址的全新对象。

4.1.3 传递参数

ECMAScript 中所有函数的参数都是按值传递的,即按4.1.2的规则进行值的复制。

var a= 1;
function addOne(num){
    num += 1;
    console.log(num)  // 输出 2
};
var res = addOne(a); 
console.log(a) // 输出 1(a的值不会因为被参数使用而改变,即基本类型的值是通过复制给到命名参数的)

下面来看看引用类型的值的情况

var person = {
    name: 'lzw'
};
function changeObj(obj){
    obj.age =18
};
changeObj(person);
console.log(person) // 输出 {name: "lzw", age: 18}(在函数内部改变参数值时,原始值也改变了,因为命名参数和person变量拥有同样的指针)

4.1.4 检测类型

typeof操作符可以用来确定变量时字符串、数值、布尔值、undefined、symbol还是函数。

typeof '' // string
typeof 1 // number
typeof true // boolean
typeof adff  // undefined(未定义变量,undefined)
typeof function a(){} // function
typeof Symbol() // symbol

但是对于null、数组、正则等就不好区分

typeof null // object
typeof {} // object
typeof [] // object
typeof new String() // object
typeof /lzw/ // object

检测引用类型时,这个操作符都返回object 如何解决这个灾难性的问题呢?使用instanceof操作符 如果变量时给定引用类型的实例,那么instanceof操作符就会返回true 首先要注意的第一点就是:所有引用类型的值都是Object的实例,所以检测引用类型值和Object构造函数时,instanceof操作符都会返回true,相应的用基本类型的值与Object检测就会返回false 眼见为实系列

[] instanceof Array // true
1 instanceof Number // false
1 instanceof Object // false
new Number(1) instanceof Number // true
[] instanceof Object // true
[] instanceof Array // true
({}) instanceof Object // true
/lzw/ instanceof Object // true
/lzw/ instanceof RegExp // true
function a(){};a instanceof Object // true
'' instanceof String // false
new String('') instanceof String // true
null instanceof Object // false

4.2 执行环境及作用域

执行环境定义了变量或函数有权访问的其他数据,决定它们各自的行为。每个环境有对应相关联的变量对象,环境中所有定义的变量和函数都保存在这个对象中。全局环境时最外围的一个执行环境,例如,在web浏览器中,全局执行环境被认为为window对象,所有全局变量和函数都是作为window对象的属性和方法创建的,直至关闭网页时,该全局执行环境才会被销毁。 眼见为实系列:

function a(){
    console.log('调用了函数a')
};
window.a() // 输出 '调用了函数a'
var b = 100;
console.log(window.b) // 输出 100

每个函数都有自己的执行环境。当执行一个函数时,该函数的环境就会被推进一个环境栈中。那么如果在执行函数过程中又遇到另一个函数呢?会再把遇到的函数的环境推进一个环境栈中,执行完后,将其环境弹出,控制权返还给之前的函数

function a(){
    console.log("执行函数a");
    b();
    console.log("继续执行函数a")
};
function b(){
    console.log("执行函数b")
};
a(); 
// 输出 '执行函数a'
// '执行函数b'
// '继续执行函数a'

代码在一个环境中执行时,会创建变量对象的一个作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。作用域链中,下一个变量对象是包含环境,再下一个变量对象为下一个包含环境,一直延伸到全局执行环境。而标识符解析也是沿着作用域链一级一级的寻找标识符的过程,逐级向后回溯,直到找到为止。

var name = "lzw";  // 函数a不会访问全局变量,因为拿到了局部变量
var b = '';
function a(){
    var name = "leezw";
    console.log(name)
    //这里可以访问局部变量name,全局变量name和b
};
// 这里全局环境可以访问全局变量name、b
a();
// 输出 'leezw'

4.2.1 延长作用域链

当执行流进入下面两个语句中之一,就会延长作用域链。

  • try-catch 语句中的catch
  • with 语句
try{
    console.log(fdhfhaf)
}catch(err){ // 创建新的变量对象里面包含被抛出的错误对象的声明
    console.log(err) //外部无法访问这个对象
}
// 输出 ReferenceError: fdhfhaf is not defined
var o ={ 
    a: 1
};
function b(){
    with(o){ // o对象被添加到作用域链最前端,所以可以访问a属性
        var c = a+ 1
    };
    return c // 没有块级作用域所以c依然可以被访问和返回
};
console.log(b())

4.2.2 没有块级作用域

js没有块级作用域。

if(true){
    var num=  1
};
console.log(num) // 输出 1

对于for循环尤为注意的一点是:

for(var i= 0;i<5;i++){
    console.log(i) //输出 1 2 3 4
};
console.log(i); // 输出 5 (因为没有块级作用域,所以在for循环外面依然可以访问)

所以,js的坑还是比较多的,又多了一个被吐槽的点,好在在es6中得到了改善,这个以后再讲。 1、声明变量

function a(){
    var num1 = 1
};
console.log(num1) // 报错 Uncaught ReferenceError: num1 is not defined

函数中变量未使用var声明,该变量会被提升到全局变量。

function a(){
    num2 = 1
};
a();
console.log(num2) // 输出 1(全局依然可以访问到num2)

严格模式下:

'use strict'
function a(){
    num3 = 1
};
a();
console.log(num3) // 报错 Uncaught ReferenceError: num3 is not defined

老是要记这个严格模式怎么办,js又一个值得吐槽的点?干脆开发中一直按严格模式要求自己来编写代码就得了。总而言之,函数内变量一定要使用var 声明,当然es6还有其他声明方法。 2、查询标识符 需要用到一个标识符时,会从作用域的前端开始,逐级向上,查询到了就停止,否则继续查询,如果一直到全局环境都没有找到则报错。

var num = 1;
function searchNum(){
    var num = 2; // 在这里找到了 num
    console.log(num) 
}
searchNum() // 输出 2
var num = 1; // 在这里找到了 num
function searchNum(){
    // var num = 2; // 注释掉以后
    console.log(num) 
}
searchNum() // 输出 1

4.3 垃圾收集

js具有自动垃圾收集机制。

4.3.1 标记清除

最常用的方式就是标记清除当变量离开环境时,则将其标记为"离开环境",环境中的变量无法访问到这些变量了,最后垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占的内存空间。

4.3.2 引用计数

4.3.3 性能问题

4.3.4 管理内存

引用解除:数据不再使用,通过将其值设置为 null 来释放其引用

function createPerson(name){
    var localPerson = new Object();
    localPerson.name = name;
    return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除 globalPerson 的引用
globalPerson = null;