学习目标:
- 每天记录一章笔记
学习内容:
JavaScript 教程---互联网文档计划
笔记时间:
2023-6-5 --- 2023-6-11
学习产出:
1.入门篇
1、JavaScript 的核心语法包含部分
基本语法+标准库+宿主API
基本语法:比如操作符、控制结构、语句
标准库:一系列具有各种功能的对象 如Array Date Math
宿主API:只能在该环境使用的接口
浏览器控制类:操作浏览器
DOM 类:操作网页的各种元素
Web 类:实现互联网的各种功能
2、ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。在日常场合,这两个词是可以互换的。
3、JavaScript 的基本语法
《1》语句
JavaScript 程序的执行单位为行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句。
var a = 1 + 3;
语句以分号结尾,一个分号就表示一个语句结束。多个语句可以写在一行内。
var a = 1 + 3 ; var b = 'abc';
分号前面可以没有任何内容,JavaScript 引擎将其视为空语句。
;;;
《2》变量
变量是对“值”的具名引用
var a = 1;
注意,JavaScript 的变量名区分大小写,A和a是两个不同的变量。
只是声明变量而没有赋值,则该变量的值是undefined。
undefined是一个特殊的值,表示“无定义”。
var a;
a // undefined
JavaScript 是一种动态类型语言,变量的类型没有限制,变量可以随时更改类型。
var a = 1;
a = 'hello';
变量提升(hoisting)
所有的变量的声明语句,都会被提升到代码的头部
console.log(a);
var a = 1;
存在变量提升,真正运行的是下面的代码
var a;
console.log(a); //undefined
a = 1;
《3》标识符
第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。
第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字0-9。
《4》注释
源码中被 JavaScript 引擎忽略的部分就叫做注释
// 这是单行注释
/*
这是
多行
注释
*/
由于历史上 JavaScript 可以兼容 HTML 代码的注释,所以<!--和-->也被视为合法的单行注释。
《5》区块
JavaScript 使用大括号,将多个相关的语句组合在一起,称为“区块”(block)
{
var a = 1;
}
a // 1
区块对于var命令不构成单独的作用域,与不使用区块的情况没有任何区别
《6》条件语句(if switch)
if结构
if (布尔值)
语句;
// 或者
if (布尔值) 语句;
if…else 结构
if (m === 3) {
// 满足条件时,执行的语句
} else {
// 不满足条件时,执行的语句
}
else代码块总是与离自己最近的那个if语句配对。
switch 结构
switch (fruit) {
case "banana":
// ...
break;
case "apple":
// ...
break;
default:
// ...
}
三元运算符 ?:
(条件) ? 表达式1 : 表达式2
《7》循环语句
循环语句用于重复执行某个操作
while 循环
while (条件)
语句;
// 或者
while (条件) 语句;
for 循环
for (初始化表达式; 条件; 递增表达式)
语句
// 或者
for (初始化表达式; 条件; 递增表达式) {
语句
}
do…while 循环
do
语句
while (条件);
// 或者
do {
语句
} while (条件);
break 语句和 continue 语句
break语句用于跳出代码块或循环
continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环
标签(label)
label:
语句
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) break top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
上面代码为一个双重循环区块,break命令后面加上了top标签(注意,top不用加引号),满足条件时,直接跳出双层循环。如果break语句后面不使用标签,则只能跳出内层循环,进入下一次的外层循环。
2.数据类型
《1》包含的所有数据类型
原始类型:number、string、boolean
合成类型:对象
两个特殊值:undefined、null
对象三个子类型
狭义的对象(object)
数组(array)
函数(function)
《2》确定一个值的数据类型
typeof运算符可以返回一个值的数据类型
typeof //运算符
instanceof //运算符
Object.prototype.toString //方法
instanceof运算符可以区分数组和对象
利用这一点,typeof可以用来检查一个没有声明的变量,而不报错
v
// ReferenceError: v is not defined
typeof v
// "undefined"
//实际编程中,这个特点通常用在判断语句
// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
null的类型是object,这是由于历史原因造成的。1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑null,只把它当作object的一种特殊值。后来null独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null返回object就没法改变了。
//详细说明每一种数据类型
《3》null 和 undefined
两者的历史原因:
1995年 JavaScript 诞生时,最初像 Java 一样,只设置了null表示"无"。根据 C 语言的传统,null可以自动转为0。
Number(null) // 0
5 + null // 5
但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。首先,第一版的 JavaScript 里面,null就像在 Java 里一样,被当成一个对象,Brendan Eich 觉得表示“无”的值最好不是对象。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果null自动转为0,很不容易发现错误。
因此,他又设计了一个undefined。区别是这样的:null是一个表示“空”的对象,转为数值时为0;undefined是一个表示"此处无定义"的原始值,转为数值时为NaN。
用法和含义
null表示空值,即该处的值现在为空
undefined表示“未定义”
// 变量声明了,但没有赋值
var i;
i // undefined
// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f() // undefined
// 对象没有赋值的属性
var o = new Object();
o.p // undefined
// 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
《4》布尔值
下列运算符会返回布尔值:
前置逻辑运算符: ! (Not)
相等运算符:===,!==,==,!=
比较运算符:>,>=,<,<=
转换规则是除了下面六个值被转为false,其他值都视为true
undefined
null
false
0
NaN
""或''(空字符串)
《5》数值
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数。
JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)
浮点数不是精确的值
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false
数值精度
第1位:符号位,0表示正数,1表示负数
第2位到第12位(共11位):指数部分
第13位到第64位(共52位):小数部分(即有效数字)
数值范围
Math.pow(2, 1024) // Infinity
Math.pow(2, -1075) // 0
特殊数值
正零和负零
几乎所有场合,正零和负零都会被当作正常的0。
唯一区别是:+0或-0当作分母,返回的值是不相等的
-0 === +0 // true
0 === -0 // true
0 === +0 // true
NaN
主要出现在将字符串解析成数字出错的场合
5 - 'x' // NaN
NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
typeof NaN // 'number'
NaN不等于任何值,包括它本身
NaN === NaN // false
数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。
NaN在布尔运算时被当作false。
NaN与任何数(包括它自己)的运算,得到的都是NaN。
Infinity
Infinity与NaN比较,总是返回false
Infinity的四则运算,符合无穷的数学计算规则
5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0
与数值相关的全局方法
1.parseInt方法用于将字符串转为整数
parseInt('123') // 123
自动去除空格
parseInt(' 81') // 81
如果parseInt的参数不是字符串,则会先转为字符串再转换
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1
进制转换
parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制。
如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略。
parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10
2.parseFloat 将一个字符串转为浮点数
parseFloat('3.14') // 3.14
parseFloat('') // NaN
3.isNaN() 判断一个值是否为NaN
isNaN(NaN) // true
isNaN(123) // false
isNaN为true的值,有可能不是NaN,而是一个字符串
isNaN('Hello') // true
// 相当于
isNaN(Number('Hello')) // true
出于同样的原因,对于对象和数组,isNaN也返回true
isNaN({}) // true
// 等同于
isNaN(Number({})) // true
isNaN(['xzy']) // true
// 等同于
isNaN(Number(['xzy'])) // true
但是,对于空数组和只有一个数值成员的数组,isNaN返回false
isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false
使用isNaN之前,最好判断一下数据类型
function myIsNaN(value) {
return typeof value === 'number' && isNaN(value);
}
isNaN 函数用于检测一个值是否为非数字值(NaN)。当传递给它的参数不是数字时,该函数会将其转换为数字。如果转换后得到的结果是非数字值,则返回 true。
然而,当传递给 isNaN 函数的参数为非字符串类型时,会发生意外的情况。例如,如果传递给 isNaN 函数的参数为对象、数组或布尔值等,那么它们会被转换为数字并返回 false。这种情况下,即使参数本身不是数字类型,isNaN 函数也会返回 false。
4.isFinite() 返回一个布尔值,表示某个值是否为正常的数值
isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true
除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true。
《4》字符串
字符串就是零个或多个排在一起的字符,放在单引号或双引号之中
单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号
'key = "value"'
"It's a long journey"
如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此。
'Did she say \'Hello\'?'
// "Did she say 'Hello'?"
"Did she say \"Hello\"?"
// "Did she say "Hello"?"
转义
需要用反斜杠转义的特殊字符
\0 :null(\u0000)
\b :后退键(\u0008)
\f :换页符(\u000C)
\n :换行符(\u000A)
\r :回车键(\u000D)
\t :制表符(\u0009)
\v :垂直制表符(\u000B)
\' :单引号(\u0027)
\" :双引号(\u0022)
\\ :反斜杠(\u005C)
《6》字符串
字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。
var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"
// 直接对字符串使用方括号运算符
'hello'[1] // "e"
如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined。
'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined
字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。
var s = 'hello';
delete s[0];
s // "hello"
s[1] = 'a';
s // "hello"
s[5] = '!';
s // "hello"
length 属性
返回字符串的长度,属性无法改变
var s = 'hello';
s.length // 5
s.length = 3;
s.length // 5
s.length = 7;
s.length // 5
《7》对象
对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
var obj = {
foo: 'Hello',
bar: 'World'
};
对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以。
var obj = {
'foo': 'Hello',
'bar': 'World'
};
如果键名是数值,会被自动转为字符串
如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。
// 报错
var obj = {
1p: 'Hello World'
};
// 不报错
var obj = {
'1p': 'Hello World',
'h w': 'Hello World',
'p+q': 'Hello World'
};
对象的每一个键名又称为“属性”(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的属性。
对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。
var obj = {
p: 123,
m: function () { ... },
}
属性可以动态创建,不必在对象声明时就指定
var obj = {};
obj.foo = 123;
obj.foo // 123
对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量
var o1 = {};
var o2 = o1;
o1.a = 1;
o2.a // 1
o2.b = 2;
o1.b // 2
如果取消某一个变量对于原对象的引用,不会影响到另一个变量
var o1 = {};
var o2 = o1;
o1 = 1;
o2 // {}
这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
var x = 1;
var y = x;
x = 2;
y // 1
如果行首是一个大括号,它到底是表达式还是语句(代码区块)?
{ foo: 123 }
为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
{ console.log(123) } // 123
上面的语句是一个代码块,而且只有解释为代码块,才能执行。
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
({ foo: 123 }) // 正确
({ console.log(123) }) // 报错
这种差异在eval语句(作用是对字符串求值)中反映得最明显。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
属性的操作
使用点运算符
使用方括号运算符
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
方括号运算符内部还可以使用表达式
obj['hello' + ' world']
obj[3 + 3]
注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。
var obj = {
123: 'hello world'
};
obj.123 // 报错
obj[123] // "hello world"
属性的赋值
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
JavaScript 允许属性的“后绑定”
var obj = { p: 1 };
// 等价于
var obj = {};
obj.p = 1;
属性的查看
Object.keys
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
属性的删除:delete 命令 删除成功后返回true
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
注意,删除一个不存在的属性,delete不报错,而且返回true。
var obj = {};
delete obj.p // true
只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除
var obj = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
delete命令只能删除对象本身的属性,无法删除继承的属性
var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }
属性是否存在:in 运算符
in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false。
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
它不能识别哪些属性是对象自身的,哪些属性是继承的
可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
属性的遍历:for…in 循环
for...in循环用来遍历一个对象的全部属性
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('键名:', i);
console.log('键值:', obj[i]);
}
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3
它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。 它不仅遍历对象自身的属性,还遍历继承的属性。
举例来说,对象都继承了toString属性,但是for…in循环不会遍历到这个属性 它默认是“不可遍历”的
var obj = {};
// toString 属性是存在的
obj.toString // toString() { [native code] }
for (var p in obj) {
console.log(p);
} // 没有任何输出
更好的做法:
var person = { name: '老张' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
《8》函数
函数的声明
(1)function 命令
function print(s) {
console.log(s);
}
(2)函数表达式
var print = function(s) {
console.log(s);
};
采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
(3)Function 构造函数
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
函数的重复声明
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
function f() {
console.log(1);
}
f() // 2
function f() {
console.log(2);
}
f() // 2
上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升(参见下文),前一次声明在任何时候都是无效的
圆括号运算符,return 语句和递归
第一等公民
函数名的提升
函数的属性和方法
name 属性
length 属性
toString()
函数作用域
定义
函数内部的变量提升
函数本身的作用域
参数
概述
参数的省略
传递方式
同名参数
arguments 对象
函数的其他知识点
闭包
立即调用的函数表达式(IIFE)
eval 命令
基本用法
eval 的别名调用
参考链接