《JavaScript高级程序设计(第3版)》 1-4章总结
作为最经典最畅销的前端书籍之一,基本成为从事前端的必读书目,所以很有必要对该书进行必要的总结,以便日后更好的理解和运用。此篇为1-4章的总结归纳。
1995 年布兰登·艾奇(Brendan Eich)开发出一款叫LiveScript 的脚本语言而后改名为JavaScript。一个完整的 JavaScript 实现应该由核心(ECMAScript)、文档对象模型(DOM)、浏览器对象模型(BOM)三个部分组成。所以我们可以使用核心语言功能去编写一些逻辑,可以用用文档对象模型(DOM)提供的方法触发某些逻辑,这些逻辑也可以改变文档对象模型(DOM)某些DOM属性,进而可以人机交互,同时浏览器对象模型(BOM)使你可以提供某些浏览器信息和操作。
<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属性的晚执行。这两个属性的出现是为了解决阻塞页面解析的问题
使用<script>
元素的方式有两种:直接在页面中嵌入 JavaScript 代码和包含外部 JavaScript
文件。
当浏览器遇到字符串""时,就会认为那是结束的标签。以下代码会发生错误:
<script type="text/javascript">
function test(){
console.log("</script>");
}
</script>
需要经过转义符
<script type="text/javascript">
function test(){
console.log("<\/script>");
}
</script>
最好的方法是通过外部文件来引入代码,具有可维护性、可缓存、适应未来等特点。
两种文档模式:混杂模式(quirks mode)和标准模式(standards mode)。未声明,则所有浏览器都会默认开启混杂模式。但是并不提倡使用混杂模式,所以一般指定某种标准模式,目前通常使用的是在文档开头添加
<noscript>
元素用以在不支持 JavaScript 的浏览器中显示替代的内容,和html5中的<video>
类似。
1、区分大小写 2、标识符:第一个字符必须是一个字母、下划线(_)或一个美元符号($);其他字符可以是字母、下划线、美元符号或数字 3、注释
// 单行注释
/*
* 这是一个多行
* 注释
*/
3、use strict 使用严格模式 4、分号结尾,代码块被{}包裹
略
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;
6 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和 String、Symbol(新增),1种复杂数据类型——Object。
使用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操作符等。这简直就是灾难。
var message;
console.log(message)// 输出undefined
console.log(typeof age) // 输出undefined
console.log(age)// Uncaught ReferenceError: 报错age is not defined
值得注意的是:未声明变量使用typeof操作符返回undefined,但是并不报错,另外对于未传递参数的参数值为undefined
空指针,所以typeof null 返回object 值得注意的是:
console.log(null == undefined) // true
undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true
眼见为实:
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
八进制(注意:严格模式下无效) 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()函数的转换规则如下。
眼见为实
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 (按十六进制解析)
所以必须指定第二个参数,否则可能转换为十六进制
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'
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
操作符用于操作数据值,有算术操作符(如加号和减号)、位操作符、关系操作符和相等操作符。
只能操作一个值的操作符叫做一元操作符 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
递增和递减操作符遵循下列规则
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()一样
眼见为实,尝试一下:
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
可用于隐形转换数据类型。
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
其他略
1、逻辑非 逻辑非操作符叹号!。 规则如下:
眼见为实:
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,而是两个操作数里面的一个,不具有短路效应
1、乘法* 2、除法/ 3、求模%
1、加法+ 注意:字符串和数字相加会变为字符串拼接
console.log(1+'1') // '11'(拼接字符串)
console.log(1+null) // 1(null转换为0)
console.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,还是挺灾难的
小于(<)、大于(>)、小于等于(<=)和大于等于(>=) 规则如下
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)
相等==和不相等!=: 转换后再比较 相等===和不相等!==: 直接比较,所以会比较数据类型 1、相等==和不相等!= 规则如下:
又是眼见为实系列:
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(数据类型不相等)
variable = boolean_expression ? true_value : false_value; 根据布尔表达式的值返回不同的结果,例如
result = true ? 1: 2;console.log(result) // 1
result = false ? 1: 2;console.log(result) // 2
var value = 1; value = vlaue + 1;
可以简化为
var value = 1; value += 1;
同理还有
这些操作符不会带来性能上的提升。
var a =1, b=2, c=3
使用逗号操作符进行多个变量声明操作。
var a = (1, 2, 3, 4, 0); // a 的值为 0
奇怪的知识又增长了
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
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的时候已经不满足条件
while(expression) statement 满足条件时再执行
var i = 0
while( i < 10 ){
i++
console.log(i)
}// 输出 1 2 3 4 5 6 7 8 9 10
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)
}
for (property in expression) statement
var o={a:1};
for(var key in o){
console.log(key,o[key])
}// 输出 a 1
注意对象属性是没有顺序的,所以for-in输出也是没法确定顺序的
label: statement
start: for (var i=0; i < 10; i++) {
alert(i);
}
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后继续循环。细微的差别还需要仔细观察,主要是跳的位置不一样,输出结果也大为不同容易混淆。 不建议过度使用,否则会把自己绕晕过去
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
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组合,不在函数中使用会报错
使用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 以后会停止并退出 严格模式下:
眼见为实:
"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
参数类型不需要指定,参数即使不传也不会有问题(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
严格模式下:
眼见为实
"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
后定义的函数会覆盖前面定义的函数
基本类型值:简单数据段(基本值数据类型:Undefined、Null、Boolean、Number和String,变量是存放在栈区的) 引用类型值:多个值构成的对象(按引用访问,栈区内存保存变量标识符和指向堆内存中该对象的指针)
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
基本类型的值:把一个基本类型的值从一个变量赋值复制到另一个变量,那么两个变量都会拥有这个值。如
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}
复制后无论对那个操作都会直接操作堆内存内的对象,进而影响所有存有该对象指针的变量。 这会给我们编写代码的时候造成的最直接麻烦就是:无法通过赋值方式获得一个拥有新的内存地址的对象,几个变量之间藕断丝连,有时候程序员就是只想改变其中之一的变量而已。 以后会介绍如何获取一个拥有新的内存地址的全新对象。
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变量拥有同样的指针)
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
执行环境定义了变量或函数有权访问的其他数据,决定它们各自的行为。每个环境有对应相关联的变量对象,环境中所有定义的变量和函数都保存在这个对象中。全局环境时最外围的一个执行环境,例如,在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'
当执行流进入下面两个语句中之一,就会延长作用域链。
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())
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
js具有自动垃圾收集机制。
最常用的方式就是标记清除。 当变量离开环境时,则将其标记为"离开环境",环境中的变量无法访问到这些变量了,最后垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占的内存空间。
略
略
引用解除:数据不再使用,通过将其值设置为 null 来释放其引用
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除 globalPerson 的引用
globalPerson = null;