进入到第三章内容,非常基础的部分,也很琐碎,整理起来确实有点难度,在脑海中难以连成一片。所以接下来的读书笔记,常见的、最显而易见的部分尽量就不罗列了,挑一些我认为比较生冷的部分加以复习。
思维导图比较杂乱,可粗看。
语法
语法部分我认为标识符和严格模式这两块可以理解一下的。
(1)标识符
标识符的命名规则值得学习,而且要求自己做到规范,以下几点,整理于本书及网上资源:
- 第一个字符必须是一个字母/下划线/美元符合;
- 其他字符可以是字母/下划线/美元符号/数字;
- 不允许包含空格和其他标点符号(文件名/目录名也不要用空格,虽然在windows系统是合法的,但别的系统中可能会出错);
- 不推荐使用特殊字符,那怕是合法的;
- 推荐使用驼峰法:一般使用小驼峰法(lower camel case),类名等使用大驼峰(upper camel case),常量可使用全大写;
- 变量命名长度应该尽可能的短,并抓住要点,尽量在变量名中体现出值的类型;
- 命名尽量语义化,尽量用英语语义,拼音有点low,尽量避免使用没有意义的命名;
- 常用的词语可用缩写
缩写 | 全称及含义 | 缩写 | 全称及含义 |
---|---|---|---|
info | information 信息 | create | 创建 |
imp | important重要的 | fn | function函数 |
init | initialization初始化、最初的 | update | 修改 |
del | delete 删除 | slct | select查询选择 |
rm | remove移除 | query | 获取 |
add | 增加 | get | 获取 |
insert | 插入 | con | content内容 |
(2)严格模式
严格模式 是在ECMAScript 5( 即 JavaScript 1.8.5 )中添加的,在旧版JavaScript本中会被忽略。现在开发中,推荐使用严格模式,可以消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为,它的好处总结为三点:
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
- 为未来新版本的Javascript做好铺垫
严格模式具体包含了以下的一些限制:
- 不允许使用未声明的变量;
- 不允许用delete方法删除变量、对象或函数;
- 不允许函数的形参重名;
- 不允许使用八进制;
- 不允许使用转义字符;
- 不允许对只读属性赋值,非严格模式下可执行赋值,但不会生效;
- 不允许对一个使用getter方法读取的属性进行赋值;
- 变量名不能使用 “eval” 和”arguments” 字符串;
- 在作用域 eval() 创建的变量不能被调用;
- 禁止的this关键字指向全局对象;使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错。
补充一个关于对象只读属性的知识:
实际上对象的数据属性包括以下4项,使用Object.defineProperty()
方法定义函数属性时需要指定这些属性
①value
对象属性的默认值,默认值为undefined
②writable
对象属性是否可修改,false
为不可修改,默认值为true
③enumerable
对象属性是否可通过for-in
循环,false
为不可循环,默认值为true
④configurable
能否使用delete
、能否需改属性特性、或能否修改访问器属性,false
为不可重新定义,默认值为true
。
来源于大神博客
变量
文中提到 “ ECMAScript 的变量是松散类型的” 。没有讲的很透彻,所以我在网上搜索了一些资料,主要有以下几点:
- 可以用来保存任何值,保存任何类型的值;
- 可以在修改变量值的同时修改值的类型;
- 可以直接初始化变量,即在定义变量的同时可以设置变量的值;
- 局部作用域中定义的变量会在函数退出后被销毁(因此,定义变量时,务必使用 var 或 let 操作符,以免局部变量成为全局变量造成混乱)。
补充知识点:js代码会有一个预加载的过程,预加载的目的是要事先构造运行环境例如全局环境,函数运行环境,还要构造作用域链,而环境和作用域的构造的核心内容就是指定好变量属于哪个范畴,因此在javascript语言里变量的定义是在预加载完成而非在运行时期。
来源于大神博客
数据类型
这部分内容很多很散,我梳理几个重要的知识点
(1)已声明未初始化变量和未声明变量
- 执行
typeof
操作符都会返回undefined
; - 已声明未初始化变量的值是
undefined
、数据类型也是undefined
; - 未声明变量的数据类型是
undefined
; - 可以使用已声明未初始化变量,但使用未声明变量会报错;
- 实际开发中,尽量显式地初始化变量,这样有助于区分未声明变量。
(2)关于NaN
- NaN,非数值,Not a Number;
- 任何涉及NaN的操作,都会返回NaN;
- NaN与任何值都不相等, 包括本身;
- isNaN()函数检查一个值是不是数值。
(3)数值转换
Number()
可以用于任何数据类型;parseInt()
和parseFloat()
专门用于把 字符串 转换为整数数值或浮点数值;- 使用
parseInt()
时小数点不是有效的数字字符,因为是整数;而使用parseFloat()
时,第一个小数点是有效浮点数字字符,第二个无效; parseInt()
支持十六进制和八进制;parseFloat()
只解析十进制值,十六进制格式转换为0;parseInt()
第二个参数可以指定进制解析字符串,支持二进制、八进制、十六进制。为避免错误解析,建议无论什么情况下都明确指定基数,一般指定10为基数。
(4)String类型相关
- 一个转义字符表示一个字符,占一个长度
let str = '\u4eac'; // 文本 “京”
console.log(str.length); //输出 1
- 数值、布尔值、对象、字符串都有
toString()
方法,null
和undefined
值没有这个方法,会报错; - 数值的
toString()
方法可以传递参数——输出数值的基数,支持二进制、八进制、十六进制,默认十进制; String()
函数能够将任何类型的值转换为字符串;
(5)Object原型的属性和方法
- 构造函数
constructor
:保存着用于创建当前对象的函数; hasOwnProperty(propertyName)
:用于检查给定的属性在当前对象实例中(不是在实例原型中)是否存在;isPrototypeOf(object)
:用于检查传入的对象是否是当前对象的原型(原型的概念会在后面的章节中提及);propertyIsEnumerable(propertyName)
:检查给定属性是否能够使用for-in语句来枚举;toLocaleString()
:返回对象的字符串表示,与执行环境的地区对应;toString()
:返回对象的字符串表示;valueOf()
:返回对象的字符串、数值或布尔值表示,通常与toString()
方法返回值相同。
操作符
我梳理几个重要的知识点
(1)操作符和函数及它们的区别
操作符,是在表达式中用于连接不同对象的运算符,不同的操作符指定了不同的运算方式。
函数,是一组一起执行一个任务的语句。
- 可以把操作符理解为语言内置的,最基础的函数,不可代替的函数;
- 操作符本质上也是函数,只是操作符是编译器需要进行进一步解释,而函数是直接调用;
- 操作符只能重载,不能自定义,函数的名字随便起,只要是个标识符就行;但操作符不行;
- 函数本身有一段代码,程序执行时,遇到函数时,会先将函数的参数入栈,再跳到函数的代码来运行。而操作符则是在本地直接运算。
(2)递增递减操作符前置和后置的区别
- 执行前置递增递减操作时,变量的值都是在语句被求值以前改变的;
- 后置递增递减操作是在包含它们的语句被求值之后才执行的。理解为其他操作执行完之后,再执行递增递减操作。
例1:递减操作符 前置
var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2; //等于 21
//此行代码执行过程中,--num1 已经执行,此时 num1 为1
var num4 = num1 + num2; //等于 21
//此时 num1 是 1
例2:递减操作符 后置
var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2; //等于 22
//此行代码执行过程中,num1 仍是 2,执行完之后才计算 num1--
var num4 = num1 + num2; //等于 21
//此时 num1 已经是 1 了
(3)逻辑与的返回规则
- 第一个操作数是对象,返回第二个操作数
- 第二个操作数是对象,只有第一个操作数求值结果为true的情况下,才会返回该对象
- 两个操作数都是对象,则返回第二个操作数
- 第一个操作数是
null
,返回null
- 第一个操作数是
NaN
,返回NaN
- 第一个操作数是
undefined
,返回undefined
逻辑与属于短路操作,如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。所以,第一个操作数是
false
,则无论第二操作数是什么值,结果都不再可能是true了。
(4)逻辑或返回规则
- 第一个操作数是对象,返回第一个操作数
- 第一个操作数的求值结果为
false
,返回第二个操作数 - 两个操作数都是对象,返回第一个操作数
- 两个操作数都是
null
,返回null
- 两个操作数都是
NaN
,返回NaN
- 两个操作数都是
undefined
,返回undefined
逻辑或也是短路操作符,第一个操作数的求值结果为
true
时,就不会对第二个操作数求值了。
开发中一个常用的技巧:给变量赋值时,可以使用逻辑或,避免赋null
或undefined
值,相当于提供一个后备值。
(5)字符串比较
- 比较字符串时,会比较两个字符串对应的字符编码值;
- 两个以字符串保存的数字字符串,比较时会依照字符编码值比较,出现意外的结果,所以应先转为数值类型;
var result1 = "23 < "3"; //true 结果不符合预期
var result2 = "23" < 3; //false
//情况1中,因为两个操作数都是字符串,会比较它们的字符编码,
//“2”的字符编码是50,“3”的字符编码是51,因此会出现这种异常。
(6)运算符优先级
这部分书中没有提及,但我认为是很重要的,MDN有最权威的资料。
访问MDN
函数
我梳理几个重要的知识点
(1)理解函数的参数
- 函数体内可以通过
arguments
对象访问参数数组; arguments
对象与数组类似,可以获取长度,可以使用方括号语法访问元素,但它不是数组的实例;- 命名的参数只是提供便利,但不是必需的。没有命名参数,可以通过
arguments
访问传入的参数; arguments
的值,永远与对应命名参数的值保持同步。但是,他们的内存空间是独立,并不是访问同一个内存空间;arguments
的长度是由传入的参数个数决定的,不是由定义函数时的命名参数个数决定的;- 没有传递值得命名参数将自动赋予
undefined
值,相当于定义了变量但又没有初始化。
(2)JavaScript函数没有重载
重载的概念:
就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
- JS没有重载,两个名字相同的函数,该名字只属于后定义的函数;
- 通过检查传入函数中参数的类型和数量,并做出不同反应,可以模仿方法的重载。
贴一个模仿重载的案例,来源于大神博客
例1: JS 函数需要实现重载的话,可以根据 arguments
对象的 length
值进行判断
function overLoading() {
// 根据arguments.length,对不同的值进行不同的操作
switch(arguments.length) {
case 0:
/*操作1的代码写在这里*/
break;
case 1:
/*操作2的代码写在这里*/
break;
case 2:
/*操作3的代码写在这里*/
break;
//后面还有很多的case......
};
};
例2: 利用闭包函数的特性,根据根据 arguments
对象的 length
值进行选择方法
//people对象,里面存着一些人名
//外国人名字组成包括first-name和last-name,就相当于我们的姓名由姓和名组成一样
var people = {
values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
};
/**
* 我们希望people对象拥有一个find方法,
* 当不传任何参数时,就会把people.values里面的所有元素返回来;
* 当传一个参数时,就把first-name跟这个参数匹配的元素返回来;
* 当传两个参数时,则把first-name和last-name都匹配的才返回来。
* find方法是根据参数的个数不同而执行不同的操作的
**/
//addMethod
function addMethod(object, name, fn) {
var old = object[name]; //把前一次添加的方法存在一个临时变量old里面
object[name] = function() { // 重写了object[name]的方法
// 如果调用object[name]方法时,传入的参数个数跟预期的一致,则直接调用
if(fn.length === arguments.length) {
return fn.apply(this, arguments);
// 否则,判断old是否是函数,如果是,就调用old
} else if(typeof old === "function") {
return old.apply(this, arguments);
}
}
}
/* 下面开始通过addMethod来实现对people.find方法的重载 */
// 不传参数时,返回peopld.values里面的所有元素
addMethod(people, "find", function() {
return this.values;
});
// 传一个参数时,按first-name的匹配进行返回
addMethod(people, "find", function(firstName) {
var ret = [];
for(var i = 0; i < this.values.length; i++) {
if(this.values[i].indexOf(firstName) === 0) {
ret.push(this.values[i]);
}
}
return ret;
});
// 传两个参数时,返回first-name和last-name都匹配的元素
addMethod(people, "find", function(firstName, lastName) {
var ret = [];
for(var i = 0; i < this.values.length; i++) {
if(this.values[i] === (firstName + " " + lastName)) {
ret.push(this.values[i]);
}
}
return ret;
});
// 测试:
console.log(people.find()); //["Dean Edwards", "Alex Russell", "Dean Tom"]
console.log(people.find("Dean")); //["Dean Edwards", "Dean Tom"]
console.log(people.find("Dean Edwards")); //["Dean Edwards"]
对于例2,很绕,我的理解是:
addMethod
方法向people.find
返回了一个闭包匿名函数,一个重要的参数fn
都还保留在内存中(三次调用的都在,仍然可以访问得到);调用people.find
的时候,会进行参数长度的比较,进而调用对应的参数。
以上,就是《Javascript高级程序设计》第三章的学习笔记。