这是ES5的入门篇教程的笔记,网址:JavaScript教程,以下内容中黑体表示大标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~
数据类型
只有var存在变量提升,let不存在变量提升。
js内部都是以64位浮点数的形式存储(64位表示精度),整数也如此,所以1.0和1是相同的一个数,1.0===1.
JavaScript 语言的底层根本没有整数。由于浮点数不是精确的值,所以涉及小数的比较和运算都不是很准确。
关于函数
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
函数参数不是必须的,JavaScript允许省略参数(与其他语言不同!!!),函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数,如下:
function f(a, b) {
return a;
}
f(1, 2, 3) // 1
f(1) // 1
f() // undefined
f(, 1) // SyntaxError: Unexpected token ,...
f(undefined, 1) // undefined 省略考前的参数,只有显式传入undefined
f.length // 2
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递,也就是说,在函数体内修改参数值,不会影响到函数外部;但是,如果是复合类型(数组、对象、其他函数),传递方式是传址传递,将会影响到原始值(如果函数内部修改的内容,不是参数对象的某个属性,而是替换掉整个参数,这是不会影响到原始值)。
var p = 2;
function f(p) {
p = 3; // 这只是一个拷贝
}
f(p);
p // 2
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4]; // 形参o的值指向的是实参obj的地址,重新对o赋值后,o会指向另一个地址,而obj指向的原地址比那个没有发生改变,所以obj不会变化。
}
f(obj);
obj // [1, 2, 3]
由于JavaScript允许函数有不定数目的参数,可以通过arguments对象读取所有参数,在严格模式下,arguments很多方法都是受到限制的;arguments很像数组,但它是一个对象(arguments是伪数组)。
什么是闭包?就是在函数的内部,再定义一个函数,用来得到函数内部的局部变量。闭包两个最大用处有两个,一个是可以读取函数内部的变量,另一个是让这些变量始终保持在内存中,也就是闭包可以使得它的诞生环境一直存在(这个作用有点不理解,需要去理解系统垃圾回收机制)。闭包还可以封装对象的私有属性和私有方法,如下(跟Java类似):
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
注: 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
在JavaScript中,圆括号()是一种运算符(以前知道圆括号是执行函数的意思,但它算运算符还真的不知道,所以人丑还是要多读书),跟在函数名之后,表示调用该函数,比如,print()就是表示调用print函数。不能再函数的定义之后加上圆括号,会产生语法错误,因为JavaScript规定function关键字出现在行首,一律解释成语句,而不是表达式,如下:
function(){ /* code */ }();
// SyntaxError: Unexpected token ...
// 以下两种方法都可以马上执行,这叫做“立即调用的函数表达式”,简称IIFE
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
关于数组
JavaScript语言规定,对象的键名一律为字符串,所以数组的键名其实也是字符串,之所以可以用数值读取,是因为非字符串的键名会被转为字符串(这个以前还真不知道)。
对象中有两种去读取成员的方法:点结构(object.key)和方括号结构(object[key])。但是,对于数值的键名,不能使用点结构。因为arr.0的写法不合法,单独的数值不能作为标识符,所以数组成员只能用方括号arr[0]表示(方括号是运算符,可以接受数值)。
清空/删除数组,可以用length属性试试哦;当然如果设置length大于数组个数时,读取新增的位置都会返回undefined。
for..in 用于对象,不推荐用于数组(因为数组也是对象,可以用非数字键加键名,for...in会遍历所有数字键和非数字键,数组遍历肯定遍历的是数字键);for()循环、while循环用于数组。(en,这个我知道)
数组的空位和undifined不一样,但是读取到的却是undefined,length属性不过滤空位,但数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。空位表示数组没有这个元素,所以不会被遍历;undefined表示数组有这个元素,但值为undefined,所以遍历不会跳过。
var a = [, , ,];
a[1] // undefined
这里说的“类似数组的对象”,在其他地方看到过其他资料,叫做“伪数组”。
根本特征:具有length属性,但是length属性不是动态值,不会随着成员的变化而变化。(其他地方也提到过,arguments也是伪数组。)
变为真正的数组,一是用slice,二时用call()。这两个内容看不懂,还需要努力!
运算符
“重载”!!!我只在Java里面听到过函数重载,js里面运算符竟然也有这种叫法,长知识了!什么叫“重载”呢?加法运算符在运行时决定,是执行相加,还是执行连接,也就是说,运算子的不同,导致了不同的语法行为,这种行为成为“重载”。(只有加法运算符才有这种操作,减法、除法和乘法都不会发生重载,因为其他的算术运算符,有一个规则:所有运算子一律转为数值,再进行相应的数学运算。)
指数运算符‘**’,这是第一次遇见,以前都是用Math.pow()来求幂的!!!
发现两者最大的区别在于编译是99**99编译时计算,Math.pow(99, 99)运行时计算,具体说明参考“常量折叠”优化技巧
‘**’指数运算符是右结合,而不是左结合,如下:
2 ** 3 **2
// 512 相当于 2**(3 ** 2)
关于相等和严格相等,这里的解释是最令人通俗易懂的!!!
‘==’比较的是两个值是否相等;‘===’表比较它们是否为“同一个值”。“同一个值”也就是不仅要比较他们的值,还要比较它们的类型。
NaN与任何值都不相等(包括自身),正0等于负0(为什么,想不通)。
复合类型的数据比较,比较的是它们是否指向同一个地址。
null和undefined与自身严格相等,与其他类型的值比较时,结果都为false,它们互相比较时结果为true。
NaN == NaN // false
+0 === -0 // true
{} === {} // false
{} == {} // false
undefined === undefined // true
null === null // true
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
undefined == null // true
二进制位运算符实际中并不常用,所以并不怎么熟悉,但是我发现了一个有趣的东西,那就是异或运算符^。
平时互换两个变量的值,一般的做法是引入一个临时变量temp,用来作为中间变量,但如果熟悉异或运算符,可以进行如下操作:
let a = 10;
let b = 99;
a ^= b, b ^= a, a ^= b;
a // 99
b // 10
12.9 ^ 0 // 12 异或运算也可以用来取整(“向下取整”,不能严格地说是向下取整)。
-12.9 ^ 0 // -12
数据类型转换
强制转换有三种Number()、String()和Boolean(),以前只会用Number(),不怎么常用后面两种,常用toString()和!!代替前面两个,也没什么区别吧。
Number函数比parseInt函数严格很多,基本上只要有一个字符无法转成数值,整个字符串就会被转为NaN。parseInt逐个解析字符,而Number函数整体转换字符串的类型。
关于Boolean(),除了undefined、null、0、NaN、'',五个值转换结果为false,其他的值全部为true,包括空对象、空数组的转换结果也为true。
parseInt('42 cats') // 42
Number('42 cats') // NaN
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(false) // false
Boolean(new Boolean(false)) // true 因为这是一个对象
null转为数值时为0,而undefined转为数值时为NaN。
错误机制处理
每次出现报错信息,都看不懂报错的原因是为什么,终于可以让我好好理解一下,因为什么原因会报错了。
Error实例对象是最一般的错误类型,在它的基础上,JavaScript还定义了其他6种错误对象,也就是说,存在Error的6个派生对象。
SyntaxError对象是解析代码时发生的语法错误。
ReferenceError对象是引用一个不存在的变量时发生的错误。
RangeError对象是一个值超出有效范围时发生的错误。
TypeError对象是变量或参数不是预期类型时发生的错误。
URIError对象是URI相关函数的参数不正确时抛出的错误。
EvalError对象是eval函数没有被正确执行时,抛出的错误,该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。
编程规范
不使用分号的三种情况
1、for和while循环,注:do…while循环是有分号的;
2、分支语句:if,switch,try
3、函数的声明语句,注:函数表达式仍然要使用分号。
for( ; ; ) {
}
while (true) {
} // 没有分号
do {
a--;
} while(a > 0); // 有分号
if (true) {
}
switch () {
}
try {
} catch {
}
function f() {
} // 没有分号
let f = function f() {
}; // 有分号
有一个重大的发现,竟然可以用对象结果代替switch…case结构!!!
function doAction(action) {
switch (action) {
case 'hack':
return 'hack';
case 'slash':
return 'slash';
default:
throw new Error('Invalid action.');
}
}
// 变身后
function doAction(action) {
let actions = {
'hack': function () {
return 'hack';
},
'slash': function () {
return 'slash';
}
};
if (typeof actions[action] !== 'function') {
throw new Error('Invalid action.');
}
return actions[action]();
}
语法专题
真是没想到console.log方法还支持占位符,可以像C语言一样用于输出不同类型的数据。
占位符有以下几种:
%s 字符串
%d 整数
%i 整数
%f 浮点数
%o 对象的连接
%c CSS格式字符串(这个是最令我好奇的)
其他的有关console方法,感觉用处不是很大,其中有个console.count()感觉还是会用到的,count方法用于计数,输出它被调用了多少次。
function greet(user) {
console.count();
return 'hi ' + user;
}
greet('bob')
// default: 1
// "hi bob"
greet('alice')
// default: 2
// "hi alice"
标准库
Object对象的原生方法分成两类:
1)Object本身的方法(又称为“静态方法”):直接定义在Object对象的方法;
2)Object的实例方法:定义在Object原型对象Object.prototype上的方法,它可以被Obejct实例直接使用。
Object.print = function (o) { console.log(o) }; // 这个Object对象本身的方法,print方法直接定义在Object对象上
Object.prototype.print = function () {
console.log(this);
};
let obj = new Object();
obj.print() // Object 凡是定义在Object.prototype对象上面的属性和方法,将被所有实例对象共享。
Object本身是一个函数,如果参数是原始类型的值,Object方法将其转为对应的包装对象的实例;如果Object方法的参数是一个对象,它总是返回该对象,即不用转换。
let obj = Object(1);
obj instanceof Object // true
obj instanceof Number // true 包括number、string、boolean
let arr = [];
let obj = Object(arr); // 返回原数组
obj === arr // true 包括数组、对象、函数
function isObject(value) {
return value === Obejct(value);
} // 可以用来判断变量是否为对象(数组、对象、函数)
Object构造函数与直接赋值是等价的。
var o1 = {a: 1};
var o2 = new Object(o1); // 两个对象指针指向同一个地址。
var o3 = new Object({a: 1}); // 不能这样赋值哦,这样赋值,重新分配了一个地址,就是一个全新的对象。
o1 === o2 // true 只适用于Object,数组不适用
o1 === o3 // false
var obj = new Object(123);
obj instanceof Number // true
通过Object.prototype.toString可以看出一个值到底是什么类型,其中返回值的第二个值表示该值的构造函数。实例对象可能会自定义toString方法,覆盖掉Object.propotype.toString方法,所以最要直接用Object.prototype.toString方法,并通过函数的call方法,可以在任意上调用这个方法。
Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"
// **比typeof运算符更准确的类型判断函数**
var type = function (o){
var s = Object.prototype.toString.call(o); // *这里的正则表达式还需下点功夫,学会手动写正则*
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"
emmm接下来的属性描述,平时不怎么接触,对这一块也很陌生,以后要多看看!!!
for...in循环:只有“可遍历”的属性,才会被for...in循环遍历,包括继承的属性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。