一 JavaScript基础
1. JavaScript初识
1. JS的三种编写方式
- 在html内部进行编写
- 在script元素内进行编写
- 在独立的JS文件中编写,并引用
<!-- 1. 在html内部进行编写 -->
<a href="#" onclick="alert('百度一下')">百度一下</a>
<!-- 2. 在script元素内进行编写 -->
<a href="#" class="google">谷歌一下</a>
<script>
var googleAEL = document.querySelector(".google");
googleAEL.onclick = function () {
alert("谷歌一下");
};
</script>
<!-- 3. 在独立的JS文件中编写,并引用 -->
<a href="#" class="bing">bing一下</a>
<script src="./js/JS的三种编写方式.js"></script>
..................................................................
var bingAEL = document.querySelector(".bing");
bingAEL.onclick = function () {
alert("bing一下");
};
2. noscript标签
<noscript>元素,用于当浏览器不支持js时
- noscript 元素用来定义在脚本未被执行时的替代内容(文本)
- 此标签可被用于可识别
<script>标签但无法支持其中的脚本的浏览器 - 如果浏览器支持脚本,那么它不会显示出
noscript元素中的文本 noscript标签中的元素中的内容只有在下列情况下才会显示出来:- 浏览器不支持脚本
- 浏览器支持脚本,但脚本被禁用
<body>
<script>
alert('您的浏览器正在允许JavaScript!')
</script>
<noscript>Your browser does not support JavaScript!</noscript>
</body>
- tips: 谷歌浏览器在 设置/隐私设置和安全性 内可以调整浏览器支不支持JS
3. JS编写的注意事项
script标签不能写成单标签元素, script标签是闭合的- 省略
type属性- 在以前的代码中,
script标签会使用type="text/javascript" - 现在可以不写这个代码了,因为JS是所有现代浏览器以及html5中的默认脚本语言
- 在以前的代码中,
- 加载顺序
- JS默认遵循html文档的加载顺序, 自上而下加载
- 推荐将js代码的位置放置在body内的最后一行
- HTML 和 CSS不区分大小写, 但在JS中严格区分大小写
- JavaScript 语句向浏览器发出的命令, 语句的作用是告诉浏览器该做什么
- 通常每条语句后面我们都会添加一个分号,表示语句的结束
- 当语句结束且存在换行符时,分号可省略
4. JS注释
tips: 文档注释在html文件内的script标签中支持度不高, 需要在js文件中才能完全生效
// 1. 单行注释
/*
2. 多行注释
*/
/**
* 此处用来给函数进行说明, 如: 给某人打招呼
* @param {string} name 姓名
- * @param {number} age 年龄
*/
// 生成文档注释, 在函数上方手打 /** 即可自动生成
function say(name, age) {
console.log("Hello" + name);
}
say();
5. JS中的变量
// 1. 变量的声明
// 2. 变量的赋值
var currentTime = "16:00";
// 3. 上述是声明时直接赋值, 可以先声明后赋值
var currentTime;
currentTime = "16:02";
currentTime = "17:32";
// 4. 可以同时声明多个变量
var name, age, sex;
name = "zgc";
age = 24;
sex = "男";
// 5. 可以同时声明并赋值多个变量
var name = "zgc", age = 18;
console.log(currentTime, name, age, sex); // 17:32 zgc 18 男
变量的命名规范:
- 第一个字符必须是一个字母/下划线(_)/美元符号($)
- 后续其他符号可以是字母/下划线(_)/美元符号($)/数字
- 不能用关键字和保留字命名
- 关键字: function/class
- 保留字: 还没有变成关键字, 但未来可能变成关键字的, 如:interface
- 变量严格区分大小写
- 变量由多个单词组成使用小驼峰标识
- 等号两边最好加上一个空格
- 语句后面可加分号, 也可不加
- 命名注意语义化表达, 见名知意
变量的注意事项:
- 一个变量未声明直接使用的话会报错
- 一个变量有声明但没有赋值, 那么默认是undefined
- 如果没有使用var声明变量也可以, 但是不推荐, 会被挂载到全局(window)对象中
6. JS中的数据类型
JavaScript中的值都具有特定的类型
JS的数据类型:
- 字符串(String)
- 数字(Number)
- 布尔(Boolean)
- 唯一标识符 (Symbol)
- 空(Null)
- 未定义(Undefined)
- 对象 (Object)
- 大整型 (BigInt)
注意: Object是复杂数据类型, 数组、函数、日期都属于 Object
typeof操作符:
typeof用于确定变量的数据类型
var info = "zgc"; // string
var info = 18; // number
var info = {
a: "zgc",
b: 18,
}; // object
function info(params) {
console.log(params);
} // function
console.log(typeof info);
// 两种用法相等, ()是将括号内的内容看作是一个整体
console.log(typeof(info));
Number类型
var num = 1;
var num = Infinity; // 表示无穷大
var num = NaN; // not a number 表示计算错误 ,如: 3 * 'abc'\
var num = 100; // 十进制
var num = 0x100; // 十六进制
var num = 0o100; // 八进制
var num = 0b100; // 二进制
// 数字可以表示的最大/最小值
var max = Number.MAX_VALUE;
var min = Number.MIN_VALUE; // 小于这个数字会被转化为 0
// isNaN, 用来判断一个值是否为NaN
console.log(isNaN(1)); // false
console.log(isNaN(NaN)); // true
String类型
var age =18
var name = "zgc"; // 双引号
var name = 'zgc' // 单引号 与双引号效果一致
var name = `zgc, ${age}` // 反引号, 可以拼接变量 zgc, 18
// 前后的引号类型必须一致
var name1 = 'my name is "zgc"' // my name is "zgc" 如果字符串里面本身包含着双引号, 可以使用单引号
var name2 = "my name is 'zgc'" // my name is 'zgc' 如果字符串里面本身包含着单引号, 可以使用双引号
// 字符串转义, 将一些特殊功能的字符通过转义字符的形式放入字符串中
// 单引号转义字符: \' 双引号转义字符: \" 反斜杠: \\ 换行: \n 回车: \r 制表符: \t 退格符: \b
var name3 = 'my name \' is "zgc"' // my name ' is "zgc"
// 获取字符串长度
var massage = 'Hello World'
console.log(massage.length);
// 字符串拼接
var name =' zgc'
var info = 'my name is'
var message = `${info}${name}` // my name is zgc
var message = info + name // my name is zgc
Boolean类型
// 布尔类型只有true\false两种值
var isLogin = true
var isAdmin = false
Undefined类型
- 最好在变量定义时进行初始化赋值, 而不是只声明一个变量
- 最好不要显示的将一个变量赋值为undefined
- 可以赋值为
'',0,null等值
- 可以赋值为
// 一个变量定义时没有赋值, 则默认为undefined
var keyword
console.log(keyword); // undefined
Null类型
- 通常用来表示对象为空,所以通常情况下, 给一个对象进行初始化时, 会赋值为null
var age = 0
var message = ''
var isOpen = false
if(age) {
// age/message/isOpen等原始类型的初始化值在分支语句中使用被转为布尔类型false
console.log('使用上述三个变量进行判断时,语句不会执行');
}
var book = {}
if(book) { // true
// 当对一个对象类型进行初始化时, 不建议初始化为{}, 而建议初始化为null
// 如果book为null的话会在分支语句中被转换为布尔值false,语句不会执行
console.log('使用上述变量进行判断时,语句会执行');
}
console.log(typeof null); // object
Object类型
- 复杂类型/引用类型
// 对象的基本使用
var user = {
name: 'zgc',
age: 21,
sex: '男'
}
// 获取到对象内的某个属性
console.log(user.sex);
7. 数据类型的转换
-
JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值
-
大多数情况下, 运算符与函数会自动地将赋予它们的值转化为正确的类型, 这是一种隐式转换
-
当然, 我们也可以通过显示的方式来对数据类型进行转换
// 一、任意类型转换为转化为字符串类型
// 1. 一个字符串类型和其他类型进行 + 操作的时候
var msg = 18 + "";
var msg = "" + true;
console.log(msg, typeof msg);
// 2. Sting(x) / x.toString()
var msg = String(18);
var msg = true.toString();
console.log(msg, typeof msg);
// 二、任意类型转换为转化为数字类型
// 1. 对其他类型进行非加法操作时
var msg = "18" / 6;
var msg = "18" - 6;
console.log(msg, typeof msg);
// 2. parseFloat(x)/parseInt(x)/Number(x)
var msg = Number(" 1 "); // 1 会自动去掉前后空格
var msg = Number(undefined); // NaN
var msg = Number("s1"); // NaN , 包含有非数字类型的字符串, 其值为NaN
var msg = Number(null); // 0
var msg = Number(true); // 1
var msg = Number(false); // 0
var msg = Number(""); // 0
console.log(msg, typeof msg);
// 三、任意类型转换为转化为布尔类型
// 1. 布尔类型中有五个falsy值,分别是:NaN, 0, null, undefined, ''
// falsy是在Boolean 上下文中认定可转换为false的值, 无论显示/隐式转化都适用
// 对象数据类型无论是空对象还是其他对象,只要是对象转换为布尔值就是true
if ("") {
console.log("判断为false,不执行");
}
// 2. Boolean(x)/ !!x
const obj = {};
console.log(!!""); // false
console.log(!!0); // false
console.log(!!"0"); // true
console.log(!!obj); // true
7. JS运算符
JavaScript 算数运算符
算数运算符用于对数字执行算数运算:
| 运算符 | 描述 |
|---|---|
| + | 加法 |
| - | 减法 |
| * | 乘法 |
| / | 除法 |
| % | 取模(余数) |
| ++ | 递加 |
| -- | 递减 |
| ** | 幂 |
JavaScript 赋值运算符
赋值运算符向 JavaScript 变量赋值。
| 运算符 | 例子 | 等同于 |
|---|---|---|
| = | x = y | x = y |
| += | x += y | x = x + y |
| -= | x -= y | x = x - y |
| *= | x *= y | x = x * y |
| /= | x /= y | x = x / y |
| %= | x %= y | x = x % y |
| **= | x **= y | x = x ** y |
JavaScript 比较运算符
| 运算符 | 描述 |
|---|---|
| == | 等于 |
| === | 等值等型 |
| != | 不相等 |
| !== | 不等值或不等型 |
> | 大于 |
| < | 小于 |
| >= | 大于或等于 |
| <= | 小于或等于 |
| ? | 三元运算符 |
// 算数运算符
console.log(5 * 2); // 10
console.log(5 / 2); // 2.5
console.log(5 % 2); // 1
console.log(2 ** 9); // 512
console.log(Math.pow(2, 9)); // 512 , Math.pow()等同于 **
// 自增/自减
// 自增/自减只能用在变量身上, 不能 5++ 这样写
var index = 5;
index++;
console.log(index); // 6
index--;
console.log(index); // 5
// 自增/自减的位置
// ++x, 先自增完成再被使用, x++, 先使用再自增
var index = 5;
++index;
console.log(index); // 6
--index;
console.log(index); // 5
var index = 5;
// console.log(index++); // 5
console.log(++index); // 6
var index = 5;
// var result = 100 + index++;
// console.log(result); // 105
// var result = 100 + ++index;
// console.log(result); // 106
// console.log(100 + ++index); // 106
console.log(100 + index++); // 105
// 赋值运算符
var x = 1;
// 链式赋值
// 从右往左进行计算
// var x = y = z = 2 + 2 等于下方写法
var x = (y = z = 2 + 2);
console.log(x, y, z); // 4 4 4
var num = 5;
console.log((num **= 2)); // 25
// 比较运算符
// 比较运算符得到的结果都是布尔类型
var num1 = 20
var num2 = 30
var result = num1 > num2
console.log(result); // false
// == 与 ===
// ==运算符, 在类型不相同的情况下, 会将运算元素先转换成number值, 在进行比较
var num1 = ''
var num2 = 0
console.log(num1 == num2); // true
// ===
// 在类型不相同的情况下直接返回false
var num1 = ''
var num2 = 0
console.log(num1 === num2); // false
2. JavaScript语句
1. 程序的执行顺序与代码块的理解
在程序开发中, 程序有三种不同的执行方式 :
- 顺序 -- 从上到下, 依次执行
- 分支 -- 根据条件判断, 决定执行代码的分支
- 循环 -- 让特定代码重复执行
// 顺序执行
var a = 1;
var b = 2;
console.log(a);
console.log(b);
// 分支语句
var isLogin = true;
if (isLogin) {
console.log("登陆成功");
}
// 循环语句
var i = 1;
while (i < 10) {
console.log(i);
i++;
}
// 一个代码块
// 代码块是多行代码的集合, 通过{}放到了一起
// 在JS中, 我们可以通过流程控制语句来决定如何执行一个代码块, 如: 分支/循环/函数等
{
var a = 1;
var b = 2;
console.log(a, b);
}
// 一个对象
var user = {
name: "zgc",
age: 18,
};
2. If分支语句
通常在写代码时,您总是需要为不同的决定来执行不同的动作。您可以在代码中使用条件语句来完成该任务。
- if 语句 - 只有当指定条件为 true 时,使用该语句来执行代码
- if...else 语句 - 当条件为 true 时执行代码,当条件为 false 时执行其他代码
- if...else if....else 语句- 使用该语句来选择多个代码块之一来执行
var score = 70;
if (score > 60) {
console.log("去游乐场"); // 执行
}
if (score > 90) {
console.log("买iPhone手机");
} else {
console.log("不买iPhone手机"); // 执行
}
if (score > 90) {
console.log("买iPhone手机");
} else if (score > 80) {
console.log("买华为手机");
} else {
// 当所有条件都不成立时执行
console.log("买小米手机"); //执行
}
// 补充1: 如果代码块中只有一行代码, 那么{}可以省略
if (score > 60) console.log("去游乐场"); // 执行
// 补充2: if()内的条件判断会隐式的转换为布尔类型
// NaN, 0, null, undefined, ''都会被转为false, 其他值会被转为true
if (!0) console.log("非0为true, 执行");
3. 三元表达式
- 使用格式: var result = condition ? value1 : value2
- condition表达式为true, 返回 value1, 否则返回 value2
var num1 = 20;
var num2 = 70;
var result = num1 - num2 > 0 ? num1 : num2 * num1;
console.log(result); // 1400
4. JavaScript 逻辑运算符
| 运算符 | 描述 |
|---|---|
| && | 逻辑与 |
| II | 逻辑或 |
| ! | 逻辑非 |
var a = 30;
var b = 70;
// 逻辑与: &&
// 条件1 && 条件2 && 条件3
// 条件都成立才为true, 只要有一个不成立就为false
if (a > 20 && b > 50) {
console.log("zgc1"); //执行
}
if (a > 20 && b > 80) {
console.log("zgc2"); //不执行
}
// 1. 将运算元转换成布尔类型
// 2. 对转成的布尔类型进行判断 如果为true, 则进行第二个运算元判断. 如果为false, 将结果(运算元)返回
// 3. 如果找到最后都为true, 则返回最后一个运算元
// 4. 只有前一个表达式为true, 才会执行后一个表达式, 当前一个条件为假时, 后面条件就不再执行了
var c = 100;
a > 20 && console.log("逻辑与, 执行");
a > 50 && console.log("逻辑与, 不执行");
console.log(a && b && c); // 100
console.log(a && b < 0 && c); // false
var user = {
friend: {
age: function () {
console.log("年龄");
},
},
};
user && user.friend && user.friend.age && user.friend.age();
// 逻辑或: ||
// 条件1 || 条件2 || 条件3
// 条件 只要有一个成立, 那么最终结果就为true
if (a > 20 || b > 50) {
console.log("zgc3"); //执行
}
if (b > 80 || a > 20) {
console.log("zgc4"); //执行
}
if (b > 80 || a > 60) {
console.log("zgc5"); //不执行
}
// 1. 将运算元转换成布尔类型
// 2. 对转成的布尔类型进行判断 如果为true 直接将结果(运算元)返回, 如果为false则进行第二个运算元判断
// 3. 如果找到最后也没有找到, 则返回最后一个运算元
// 4. 当前一个条件为真时, 后面条件就不再执行了
var name = "wf";
var error = NaN;
var msg = "";
var age = 0;
console.log(name || a || b); // wf
console.log(error || a || age); // 30
console.log(error || msg || age); // 0
var info = msg || "个人信息";
console.log(info); // 个人信息
// 逻辑非: !
// 用于取反
var a = 0;
if (a) {
console.log("逻辑非, 不执行");
}
if (!a) {
console.log("逻辑非, 执行");
}
// !!转化为布尔类型
console.log(!!a); // false
console.log(!!b); // true
5. switch语句
- 与if语句不同的是, switch语句只能做值的相等判断 (注意: 这里是使用全等运算符===进行判断), 而if语句可以做值的范围判断
- switch语句至少包含一个case和一个可选的default代码块
- 首先设置表达式 n(通常是一个变量), 随后表达式的值会与结构中的每个 case 的值做比较
- 如果存在匹配,则与该 case 关联的代码块会被执行, 请使用 break 来阻止代码自动地向下一个 case 运行
- 可以添加 花括号
{}生成一个单独的块级作用域
// switch (表达式) {
// case n:
// 代码块;
// break;
// case n:
// 代码块;
// break;
// default:
// 默认代码块;
// }
// 对于重复使用的变量最好定义在 switch 语法外
// 对于不重复使用的变量,需要在case里声明一个{}块级作用域
var day;
switch (
new Date().getDay() // getDay() 方法返回 0 至 6 的数字周名。
) {
case 0:
day = "星期天";
break;
case 1:
day = "星期一";
break;
case 2: {
// 单独作用域
var age = 18;
day = "星期二";
console.log(age);
break;
}
case 3:
day = "星期三";
break;
case 4:
day = "星期四";
break;
case 5:
day = "星期五";
break;
case 6:
day = "星期六";
break;
default:
day = "";
}
console.log(day);
6. while/do..while
// while
// - 当条件成立时,执行代码块
// - 当条件不成立时, 跳出代码块
// while(循环条件){
// 循环代码块
// }
var num1 = 0;
while (num1 < 3) {
console.log(num1);
num1++;
} // 0 1 2
// do..while
// 不管条件是否成立, do循环体都会先执行一次
// do {
// 循环代码块;
// } while (循环条件);
var num2 = 0;
do {
console.log(num2);
num2++;
} while (num2 < 3); // 0 1 2
7. for
for(begin; condition; step){
循环代码块
}
- begin: 今日循环时执行, 只执行一次
- condition: 每次循环之前判断, 如果条件成立, 执行代码块
- step: 每次循环代码块执行完成之后执行
for (let i = 0; i < 3; i++) {
console.log(i);
} // 0 1 2
// 循环的嵌套
for (let i = 0; i < 2; i++) {
console.log("开始");
for (let j = 0; j < 2; j++) {
console.log("a");
}
console.log("结束");
} // 开始 a a 结束 开始 a a 结束
// 案例一: 在页面中打印心型
for (let i = 0; i < 6; i++) {
document.write("<div>♥ ♥ ♥ ♥ ♥ ♥</div>");
}
document.write("-------------");
// 案例二: 打印心型强化版
for (let i = 0; i < 6; i++) {
document.write("<div>");
for (let j = 0; j < 6; j++) {
document.write(" ♥ ");
}
document.write("</div>");
}
document.write("-------------");
// 案例三: 在页面中打印心型三角
// 1:
var k = 0;
for (let i = 0; i < 6; i++) {
k++;
document.write("<div>");
for (let j = 0; j < k; j++) {
document.write(" ♥ ");
}
document.write("</div>");
}
document.write("-------------");
// 2:
for (let i = 0; i < 6; i++) {
k++;
document.write("<div>");
for (let j = 0; j < i + 1; j++) {
document.write(" ♥ ");
}
document.write("</div>");
}
九九乘法表:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
table {
border-collapse: collapse;
}
td,
th {
padding: 6px 12px;
border: 1px solid #000;
}
</style>
</head>
<body>
<script>
document.write("<table>");
for (let i = 0; i < 9; i++) {
document.write("<tr>");
for (let j = 0; j < i + 1; j++) {
document.write(
` <td>${j + 1} x ${i + 1} = ${(j + 1) * (i + 1)}</td> `
);
}
document.write("</tr>");
}
document.write("</table>");
</script>
</body>
</html>
8. 循环的控制--break/continue
- break: 直接跳出循环, 不再执行循环体内的代码, 但如果循环在函数中, 且函数内(循环外)有其他语句, 其他语句会执行
- continue: 跳过本次循环, 循环内continue后的代码都不会执行, 执行下一次循环
- return: 直接跳出当前的方法(函数), 返回到该调用的方法的语句处, 继续执行. 即函数内部(方法外)return后面的语句也不会再执行
const users = ["a", "b", "c", "d"];
for (let i = 0; i < users.length; i++) {
console.log(users[i]);
if (users[i] === "c") break;
console.log("结束");
} // a 结束 b 结束 c
const nums = ["1", "2", "3", "4"];
for (let i = 0; i < nums.length; i++) {
console.log(nums[i]);
if (nums[i] === "2") continue;
console.log("结束");
} // 1 结束 2 3 结束 4 结束
9. 综合练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// Math.random(): [0, 1)
// Math.floor(): 向下取整
// 1. 生成 0到 99 的数字
var randomNum = Math.floor(Math.random() * 100);
// 2. 玩家有七次猜数字机会
for (var i = 0; i < 7; i++) {
// 3. 获取用户的输入, prompt()返回的是字符串类型的值
var result = Number(prompt("请输入您猜测的数字"));
// 4. 和randomNum进行比较
if (result === randomNum) {
alert("恭喜你猜对了");
break;
} else if (result > randomNum) {
alert("你猜大了");
} else {
alert("你猜小了");
}
// 5. 当最后都没有才出来时, 也可以使用变量来记录是否猜中, 如果猜中则为true
if (i == 6) {
alert("你的次数用完了");
}
}
</script>
</body>
</html>
3. JavaScript函数
1. 函数的声明与使用
- 函数命名尽量做到语义化表达
- 函数定义时里面的代码不会执行, 函数必须调用才能执行
- 函数开可以多次调用, 调用几次执行几次
// function 函数名() {
// 函数封装的代码;
// }
// 调用 : 函数名()
function sayHello() {
console.log("Hello");
}
sayHello();
sayHello();
2. 函数的参数与返回值
- 形参(parmaters): 用来接收实际参数, 在函数内部作为变量使用
- 实参(arguments): 实际的参数, 用来把参数传递到函数内部
var 变量 = 函数名()函数执行后会返回一个值, 使用变量来进行接收- 可以使用
return关键字来返回结果 - 一旦函数中执行
return语句, 那么当前函数就会被终止 - 函数都是有返回值的, 如果没有使用
return语句, 那么函数有默认的返回值undefined - 如果使用
return语句, 但是return后面没有任何值, 那么函数的返回值也是undefined
// 1. 函数的参数
function sayHello(name, age) {
// name, age: 形参, parmaters
console.log(`Hello, my name is ${name}, ${age} yaers old`);
}
sayHello("zgc", 18); // "zgc", 18: 实参, arguments
// 2. 函数的返回值
// var 变量 = 函数名() 函数执行后会返回一个值, 使用变量来进行接收
// 可以使用return关键字来返回结果
// 一旦函数中执行return语句, 那么当前函数就会被终止
// 函数都是有返回值的, 如果没有使用return语句, 那么函数有默认的返回值`undefined`
// 如果使用return语句, 但是return后面没有任何值, 那么函数的返回值也是undefined
function foo(name) {
return name === "zgc";
console.log("这句不会执行");
}
var bar = foo("zgc");
console.log(bar); // true
3. 数字的格式化工具
var count1 = 13687; // 13687
var count2 = 5433332; // 543万
var count3 = 8766633333; // 87亿
function formatCount(count) {
var result = 0;
if (count >= 10_0000_0000) {
// _: es6语法糖, 无实际作用, 只是让数字更加直观, 可以加在数字的任何位置
result = Math.floor(count / 1_0000_0000) + "亿";
} else if (count >= 10_0000) {
result = Math.floor(count / 1_0000) + "万";
} else {
result = count;
}
return result;
}
var result1 = formatCount(count1);
console.log(result1);
var result2 = formatCount(count2);
console.log(result2);
var result3 = formatCount(count3);
console.log(result3);
4. arguments初识
- 在箭头函数中没有arguments
function foo(name, age) {
// 函数中都存在着一个变量arguments
console.log(arguments);
// arguments是一个对象
console.log(typeof arguments);
// 对象内部包含了所有传入的参数, 即使没有通过形参接收
console.log(arguments[0], arguments[1], arguments[2]); // zgc 19 1.88
}
foo("zgc", 19, 1.88);
// 案例: 求和
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
var num = arguments[i];
total += num;
}
return total;
}
console.log(sum(10, 20)); // 30
console.log(sum(10, 20, 30)); // 60
5. 函数的递归调用
- 递归必须要有结束条件, 否则会产生无限调用, 造成bug
// 1. 函数调用其他函数
function bar() {
console.log("bar函数执行");
}
function foo() {
console.log("top");
console.log("foo函数执行");
bar();
console.log("bottom");
}
foo();
// 结果:
// top;
// foo函数执行;
// bar函数执行;
// bottom;
// 2. 函数调用自己, 即递归
// 递归必须要有结束条件, 否则会产生无限调用, 造成bug
// 3. 案例: 封装一个函数, 函数可以实现x的n次方
// 方法1: 循环实现
function pow2(x, n) {
if (n === 0) return 1;
var result = 1;
for (var i = 0; i < n; i++) {
result *= x;
}
return result;
}
console.log(pow2(5, 3)); // 125
// 方法2: 递归实现(必须有一个结束条件)
// 递归的性能不如for循环, 但写出来的代码简洁
function pow3(x, n) {
if (n === 0) return 1;
if (n === 1) return x;
return x * pow3(x, n - 1);
}
console.log(pow3(5, 3)); // 125
6. 斐波那契数列
// 斐波那契数列: 1 1 2 3 5 8 13 21 34 55
// 1. 递归实现
function getNum1(n) {
if (n === 1 || n === 2) return 1;
return getNum1(n - 1) + getNum1(n - 2);
}
console.log(getNum1(6)); // 8
console.log(getNum1(10)); // 55
// 2. for实现
function getNum2(n) {
if (n === 1 || n === 2) return 1;
var n1 = 1;
var n2 = 1;
var num = 0;
for (var i = 3; i <= n; i++) {
num = n1 + n2;
n1 = n2;
n2 = num;
}
return num;
}
console.log(getNum2(6)); // 8
console.log(getNum2(10)); // 55
7. 局部-全局-外部变量
- 在ES5之前, JS是没有块级作用域的概念, 但函数是可以定义自己的作用域的
- 作用域: 表示一些标识符的有效范围
- 函数的作用域表示在函数内部定义的变量, 只有在函数内部可以被访问到
- 全局变量: 在全局(script)定义一个变量, 那么这个变量可以在定义之后的任何范围内被访问到, 那么这个变量就称之为是一个全局变量
- 通过var声明的全局变量会在window对象上添加一个属性
- 局部变量: 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量
- 外部变量: 在函数内部去访问函数之外的变量, 称之为外部变量
- 在函数内部访问变量, 先在自己的内部寻找, 如果没有找到就往上层作用域寻找, 一直到window对象都没有找到的话就报错
// 1. 全局变量: 在全局定义一个变量, 那么这个变量可以在定义之后的任何范围内被访问到, 那么这个变量就称之为是一个全局变量
var message = "Hello World";
if (true) {
console.log(message); // Hello World
}
function foo() {
// 2. 局部变量: 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量
var nickname = "zgc";
console.log("foo", message); // foo Hello World
console.log("foo", nickname); // foo zgc
function bar() {
// 3. 外部变量: 在函数内部去访问函数之外的变量, 称之为外部变量
console.log("bar", message); // bar Hello World
console.log("bar", nickname); // bar zgc
}
bar();
}
foo();
// console.log("foo", nickname); // nickname is not defined
// 4. 变量的访问顺序
// 在函数内部访问变量, 先在自己的内部寻找, 如果没有找到就往上层作用域寻找, 一直到window对象都没有找到的话就报错
var info = "wlc";
function name() {
var info = "zgc";
function bar() {
// var info = "wf";
console.log("访问顺序", info);
}
bar();
}
name(); //访问顺序 zgc
8. 函数式编程
- 函数可以作为一等公民
- 函数可以赋值给变量(函数表达式写法)
- 函数可以在变量之间来回传递
- 函数可以作为另一个函数的参数
- 函数作为另一个函数的返回值
- 函数存储在另一个数据结构中
- 函数式编程: 通常我们对函数作为头等公民的编程方式, 称之为函数式编程
// 1. 函数的声明(推荐)
function foo() {
console.log("foo");
}
// 2. 函数的表达式
var bar = function () {
console.log("bar");
};
// 3. 函数可以作为一等公民
// 函数可以赋值给变量(函数表达式写法)
var bar1 = function () {
console.log("bar");
};
// 函数可以在变量之间来回传递
var bar2 = bar1;
bar2(); // bar
// 函数可以作为另一个函数的参数
function foo1(fn) {
fn();
}
foo1(bar1); // bar
// 函数作为另一个函数的返回值
function foo2() {
return bar;
}
foo2()(); //bar
// 函数存储在另一个数据结构中
var obj = {
name: "zgc",
eating: function () {
console.log("eating");
},
};
obj.eating(); // eating
// 4. 函数式编程: 通常我们对函数作为头等公民的编程方式, 称之为函数式编程
9. 函数回调与匿名函数
- 回调函数被认为是一种高级函数,一种被作为参数传递给另一个函数(在这称作"otherFunction")的高级函数,回调函数会在otherFunction内被调用(或执行)。
- 如果在传入一个函数时, 没有指定函数的名称或者通过函数表达式指定函数对应的变量, 那么这个函数称之为匿名函数
function foo(fn) {
// 回调函数: 通过fn调用bar函数的过程, 称之为函数的回调
fn();
}
function bar() {
console.log("bar");
}
bar(); // 函数的调用
foo(bar);
// 案例:
function request(url, callback) {
// .....
var list = ["a", "b"];
callback(list);
}
// function getList(list) {
// console.log(list);
// }
// request("www", getList);
// 匿名函数写法:
request("www", function (res) {
console.log(res);
});
10. 立即执行函数
- IIFE(立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript函数
- 第一部分是包围在 圆括号运算符
()里的一个匿名函数,这个匿名函数拥有独立的词法作用域。 - 第二部分再一次使用
()创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。
- 第一部分是包围在 圆括号运算符
- 当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
- 将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button class="btn">1</button>
<button class="btn">2</button>
<button class="btn">3</button>
<button class="btn">4</button>
<script>
// 1. 立即执行函数
// (function () {
// 代码块;
// })();
// 当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
(function () {
var nickname = "Barry";
})();
// 无法从外部访问变量 name
// console.log(nickname); // 抛出错误:"Uncaught ReferenceError: name is not defined"
// 将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。
var result = (function () {
var name = "Barry";
return name;
})();
// IIFE 执行后返回的结果:
console.log(result); // "Barry"
// 2. 应用:
// (1) 防止全局变量的命名冲突
// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 写法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
})();
// (2) 形成单独的作用域, 避免外界访问和修改内部变量
var btnEls = document.querySelectorAll(".btn");
for (var i = 0; i < btnEls.length; i++) {
var btn = btnEls[i];
btn.onclick = function () {
// onclick回调是在点击时才会触发, 在这时for循环早已经执行完毕
alert(i); // 结果总是4, 而不是0,1,2, 3
};
}
// 解决方法:
// 1. for循环中用let代替var
// 2. 立即执行函数
var btnEls = document.querySelectorAll(".btn");
for (var i = 0; i < btnEls.length; i++) {
var btn = btnEls[i];
(function (m) {
// 将i当参数传递进去, 立即执行函数有单独的作用域, 当点击事件回调时读取到作用域中的参数 m
// i 是实参, m 是形参
btn.onclick = function () {
alert(m); // 0, 1, 2, 3
};
})(i);
}
</script>
</body>
</html>
11. this关键字
在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了,也就是说,this的指向完全取决于函数调用的位置。
- 函数调用: 当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象,立即执行函数,默认的定时器等函数,this也是指向window。
- 方法调用: 如果一个函数作为一个对象的方法来调用时,this指向这个对象。
- 构造函数调用: this指向这个用new新创建的对象。
- apply 、 call 和 bind 调用模式: 这三个方法都可以显示的指定调用函数的 this 指向。
- 箭头函数的this: 指向声明时所在作用域下 this 的值,即箭头函数的this去他的上级作用域下寻找,任何方法都改变不了他的指向
// 1. 函数调用
function foo() {
console.log(this === window); //true
}
foo();
// 2. 方法调用
var obj = {
name: "zgc",
running: function () {
// 这里的上级作用域是window, 对象是数据类型, 不是代码块, 没有作用域
console.log(this);
},
};
obj.running(); //obj对象
var fn = obj.running;
fn(); // window对象
function bar() {
console.log(this);
}
var baz = {
name: "wf",
bar: bar,
};
baz.bar(); // baz对象
4. JavaScript对象
1. 对象的基本使用
- 对象只是一种特殊的数据。对象拥有属性和方法;
- 对象内部属性由
key: value的键值对组成, 其中key是字符串类型(大部分情况下引号会被省略,另外es6后可以用Symbol类型),vaule可以是任意类型, 包括基本数据类型, 对象, 函数等; - 点符号要求key是有效的变量标识符,不能包含空格, 不能以数字开头, 也不能包含某些特殊字符, 这个时候我们可以使用
[]
// 1. 对象的基本结构
var person = {
name: "zgc",
age: 18,
eat: function () {
console.log("eating");
},
// 这种情况下需要引号
"my friend": "wf",
};
console.log(person);
// 2. 对象的常见操作
// (1) 对象的创建方式:
// 方式1:字面量创建
var obj1 = {
name: "zgc",
age: 10,
running: function () {
console.log("running");
},
};
// 方式2: new Object
var obj2 = new Object();
// 方式3: new 其他类
function Person() {}
var obj3 = new Person();
// (2) 对象的增删改查
// 查询对象属性
console.log(obj1.age, obj1.name); // 10 'zgc'
obj1.running(); // running
// 修改对象属性
person.age = 40;
console.log(person.age); // 40
// 添加对象属性
obj2.name = "zgc";
obj2.study = function () {
console.log("study");
};
console.log(obj2);
obj2.study(); // study
// 删除对象属性
// delete操作符
delete person.name;
console.log(person);
// 3. 方括号的使用
// 点符号要求key是有效的变量标识符,不能包含空格, 不能以数字开头, 也不能包含某些特殊字符, 这个时候我们可以使用[]
var message = "Hello World";
var info = {
name: "zgc",
age: 29,
"my friend": "wf",
"paly games": function () {
console.log("games");
},
[message]: "你好, 世界",
};
console.log(info.name === info["name"]); //true
console.log(info["my friend"]); // wf
info["paly games"](); // games
var play = "paly games";
info[play](); // games
console.log(info[message]); // 你好, 世界
2. 对象的遍历
var info = {
name: "zgc",
age: 18,
height: 1.88,
};
// Object.keys(): 拿到对象的所有属性名, 返回一个由属性名组成的数组
// 1. for
const arr = Object.keys(info);
console.log(arr); // ['name', 'age', 'height']
for (var i = 0; i < arr.length; i++) {
var value = info[arr[i]];
console.log(value); // zgc 18 1.88
}
console.log("---------------------------");
// 2. for in
for (var key in info) {
console.log(key);
console.log(info[key]);
}
// name;
// zgc;
// age;
// 18;
// height;
// 1.88;
// 3. for of: 默认情况下是不能遍历对象的
for (var key of info) {
// 报错: info is not iterable
}
3. 内存
- 程序是需要加载到内存中来执行的, 我们可以将内存划分为两个区域: 栈内存和堆内存;
- 原始类型占据的空间是在 栈内存(stack) 中分配的
- 对象类型占据的空间是在 堆内存(heap) 中分配的
- 值(基础)类型:直接存储在栈中的数据,其操作方式为传值;
- 引用类型:存储在栈中的是该对象的引用,真实的数据存放在堆内存里,其操作方式为传址;
// 1.
var obj = {
name: "zgc",
age: 18,
};
var info = obj;
obj.age = 20;
console.log(obj.age, info.age); // 20 20
var msg = "Hello";
var detail = msg;
console.log(msg, detail); // Hello Hello
msg = "World";
console.log(msg, detail); // World Hello
var obj2 = {};
var obj3 = {};
// 2.
console.log(info === obj); // true;
console.log(obj2 === obj3); // false;
var msg1 = "Hello";
console.log(msg === msg1); //true
var user = {
name: "zgc",
friend: {
name: "wf",
},
};
var friend = user.friend;
friend.name = "wlc";
console.log(user.friend.name); // wlc;
// 3.
function foo(a) {
a = 200;
}
var baz = 100;
foo(baz);
console.log(baz); // 100
// 4.
function foo1(a) {
a.age = 18;
// 创建了一个新对象, 没有对传入对象进行修改
a = {
name: "wf",
};
}
var baz = {
name: "zgc",
};
foo1(baz);
console.log(baz); // {name: 'zgc', age =18}
4. 工厂函数
每个对象都是通过工厂造就的全新的对象。
优势:
- 工厂函数可以解决创建多个类似功能对象的问题。
缺点:
- 工厂模式无法解决对象的识别问题: 不能明确表示对象是什么类型的, 都是Object类型
function createPerson(name, age, height) {
let o = new Object();
o.name = name;
o.age = age;
o.height = height;
o.sayName = function () {
console.log(this.name);
};
return o;
}
function createStudent() {
let o = {};
return o;
}
let person1 = createPerson("zgc", 23, "1.88");
let person2 = createPerson("wf", 25, "1,89");
let student = createStudent();
console.log(person1, person2, student);
// 在打印对象时, 都是Object类型的对象: Object Object Object
5. 构造函数
相比工厂函数, JS已经默认提供了一种更加符合JavaScript思维方式(面向对象的思维方式)的创建对象的规则
- 构造函数也称之为构造器, 通常是我们在创建对象时会调用的函数
- 构造函数的函数名通常使用大写字母开头
如果一个函数被new操作符调用 :
- 首先在堆内存中创建了一个新的空对象
- 设置原型,将对象的原型设置为构造函数的prototype属性。
- 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
- 判断函数的返回值类型,如果是值类型,返回创建的对象(注: 不写
rteturn默认返回undefined, 是值类型)。如果是引用类型,就返回这个引用类型的对象。
function Person(name, age, height) {
this.name = name;
this.age = age;
this.height = height;
this.sayName = function () {
console.log(this.name);
};
}
function Student() {}
var person1 = new Person("zgc", 23, "1.88");
var person2 = new Person("wf", 25, "1,89");
var student = new Student();
console.log("构造函数", person1, person2, student);
// 在打印对象时, 可以看到对象的具体类型: Person Person Student
6. 对象额外补充
// 1. 全局对象: 浏览器中存在一个全局对象window
console.log(window);
// 作用1: 查找变量时, 最终会找到window身上, 如果window也不存在该变量就会报错
// 作用2: 将一些浏览器全局提供给我们的变量/方法/对象放在window上面, 方便我们使用
// 作用3: 使用var定义的全局变量会被添加到window上面
var message = "zgc";
console.log(window.message); // zgc
function foo() {
// abc(); // abc is not defined
alert("zgc"); // alert方法在window上, 所以能够使用
console.log(window.console === console); // true
}
// foo();
// 2. 函数也是一个对象
var foo = function () {}; // 存储在堆内存中
function bar() {} // 存储在堆内存中
var baz = foo; // 存储在堆内存中, baz和foo都是同一个内存地址, 指向同一块存储数据
7. 原始类型的包装类型
JavaScript的原始类型并非对象类型, 所以从理论上来讲, 它们是没有办法获取属性和调用方法的; 但是在实际上,每当读取一个基本类型的时候,js内部会自动创建一个基本包装类型对象,可以让我们调用一些方法来操作; 该包装类型只存在于一行代码的执行瞬间,然后立即销毁
juejin.cn/post/692304… segmentfault.com/a/119000000…
- js中为了便于基本类型操作,提供了5个特殊的引用类型:
Boolean、Number、String、Symbol、BigInt它们具有基本类型特殊行为。 - 即除了
null 和 undefined,所有的原始值都有等价的、由对象包装原始值的形式表达,null 和 undefined没有任何方法. 也没有对应的对象包装类型 - 引用类型与基本包装类型的主要区别就是对象的生存期
使用
new操作符创建的引用类型的实例,在执行流离开当前作用域之前,会一直保存在堆内存中。而后台自动创建的基本包装类型的对象,则只存在一行代码的执行瞬间,然后立即被销毁。这意味着我们不能为基本类型的值添加属性和方法。
// JavaScript的原始类型并非对象类型, 所以从理论上来讲, 它们是没有办法获取属性和调用方法的
// 但是, 在开发中我们经常会看到类似操作:
var message = "Hello World";
console.log(message.length); // 11
var num = 12.34567;
console.log(num.toFixed(2)); // 12.35
// 为什么会出现这种现象呢?
// 原始类型是简单的值, 默认不能调用属性和方法
// 这是因为JS为了使其获取属性和调用方法, 对其封装了对应的包装类型
// 案例:
var str = "hello zgc";
var str1 = str.substring(2);
// 等价于:
var str = new String("hello zgc"); // 创建String类型的实例, 浏览器内部默认执行
var str1 = str.substring(2); //在实例上调用指定的属性或方法
str1 = null; // 销毁这个实例
// 不能为基本类型的值添加属性和方法
var str = 'zgc'
str.age = 23
console.log(str.age) // undefined
8. 包装类型-Number类型
// Number实例方法:
var num = 3.1415926; // 等价于new Number(3.1415926) -> 包装类型
// 1. toString(base), 将数字转化为字符串, 并且按照base进制进行转化
// base的范围是 2 - 36, 默认情况下是10
var str = num.toString();
console.log("str", str, typeof str, "num", num, typeof num);
// str 3.1415926 string num 3.1415926 number
console.log(num.toString(2)); // 转化为2进制的字符串类型
console.log(num.toString(8)); // 转化为8进制的字符串类型
console.log(num.toString(16)); // 转化为16进制的字符串类型
// 2. toFixed(digits) 格式化一个数字, 保留digits小数位, 会进行四舍五入
// digits的范围是 0 - 20(包含) 之间
var pi = num.toFixed(3); // 返回值是string类型
console.log(pi); // 3.142
// Number类方法:
var msg = "123.321";
console.log(Number(msg)); // 123.321
// 1. Number.parseInt(string, radix), 将字符串解析成整数, 也有对应的全局方法parseInt
// radix(可选 ):表示要解析的数字的基数。该值介于 2 ~ 36 之间。
// 值不会进行四舍五入
console.log(Number.parseInt(msg)); // 123
// 在window上的方法
console.log(parseInt(msg)); // 123
// 2. Number.parseFloat(), 将字符串解析成浮点数, 也有对应的全局方法parseFloat
console.log(Number.parseFloat(msg)); // 123.321
console.log(parseFloat(msg)); // 123.321
console.log(window.parseInt === Number.parseInt); //true
9. 内置对象-Math
- Math是一个内置对象(不是构造函数), 它拥有一些数学常数属性和数学函数方法
// Math是一个内置对象(不是构造函数), 它拥有一些数学常数属性和数学函数方法
console.log(typeof Number); // function
console.log(typeof Math); // object
// 1. pi的值
console.log(Math.PI); // 3.141592653589793
// 2. 向上舍入取整
console.log(Math.ceil(3.55)); // 4
// 3. 向下舍入取整
console.log(Math.floor(3.55)); // 3
// 4. 四舍五入取整
console.log(Math.round(3.55)); // 4
// 5. 生成 [0, 1)的随机数
console.log(Math.random());
// 6. Math.pow(x, y) x的y次幂
console.log(Math.pow(2, 3)); // 8
// 7. 取绝对值
console.log(Math.abs(-32)); // 32
// 案例1: 生成 [5, 50) 的随机整数
var num = Math.floor(Math.random() * 45) + 5;
console.log(num);
10. 包装类型-String类型
基本使用
var msg = "Hello World";
// 1. 获取字符串长度
console.log(msg.length); // 11
// 2. 返回在指定位置的字符
console.log(msg[4]); // o
console.log(msg.charAt(0)); // H
// 这两种方法的区别是, 访问不到字符位置时charAt返回一个空字符串, 索引方式返回undefined
// 3. 字符串遍历
// for普通遍历
for (var i = 0; i < msg.length; i++) {
console.log(msg[i]);
}
// for...of
// 对象默认不支持for...of
// String对象内部是将字符串变成了一个可迭代对象, 所以能够使用for...of
for (var char of msg) {
console.log(char);
}
修改字符串
- 字符串在定义后是不可以修改的, 所以下面操作是没有意义的
- 但是可以给变量重新赋值新的字符串
msg[2] = "q";
console.log(msg); // my name is zgc
// msg中所有字符大写 toUpperCase
// msg中所有字符小写 toLowerCase
var msg1 = msg.toUpperCase(); // 根据原来字符串生成新的字符串, 原字符串没有改变
console.log(msg1, msg); // MY NAME IS ZGC my name is zgc
// 替换字符串
var msg2 = msg.replace("zgc", "wf"); // 根据原来字符串生成新的字符串, 原字符串没有改变
console.log(msg2, msg); // my name is wf my name is zgc
查找字符串
- 在一个字符串中查找或者获取另一个字符串
// (1) indexOf(): 对大小写敏感!
// 搜索到, 返回字符串所在的索引位置, 没有搜索到返回-1
var str = "Hello world!";
console.log(str.indexOf("Hello")); // 0
console.log(str.indexOf("World")); // -1
console.log(str.indexOf("world")); // 6
// (2) includes(): 返回布尔值,表示是否找到了参数字符串
let s = "Hello world!";
console.log(s.includes("Hello")); // true
// 支持第二个参数,表示开始搜索的位置
console.log(s.includes("Hello", 6)); // false
console.log(s.includes("world", 6)); // true
获取子字符串
- slice: 提取字符串的片断,并在新的字符串中返回被提取的部分, 原字符串不变
- slice(start,end); 取值范围:
[start,end), 且允许负值 - start :要抽取的片断的起始下标。如果是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。
- end:紧接着要抽取的片段的结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。
var str = "abc def ghk";
var newStr1 = str.slice(6);
console.log(str, newStr1); //abc def ghk f ghk
var newStr2 = str.slice(2, 5);
console.log(newStr2); // c d
var newStr3 = str.slice(4, -1);
console.log(newStr3); // def gh
其他常用方法补充
// 6. 字符串拼接
// 方式1: concat()
var a = "abc";
var b = "def";
var c = "gh";
var d = a.concat(b).concat(c);
var e = a.concat(b, c, "zgc");
console.log(d); //abcdefgh
console.log(e); //abcdefghzgc
// 方式2: +
var f = a + b + c;
console.log(f); // abcdefgh
// 7. 删除首尾空格: str.trim()
console.log(" zgc ".trim());
// 8. 字符串切割 split(): 把字符串分割为字符串数组
// 原字符串不变
var message = "abc-def-ghi-jkl";
var str = "abc def ghi jkl";
console.log(str.split(" ")); //["abc", "def", "ghi", "jkl"]
console.log(message.split("-")); //["abc", "def", "ghi", "jkl"]
console.log(str.split("")); //["a", "b", "c", " ", "d", "e", "f", " ", "g", "h", "i", " ", "j", "k", "l"]
console.log(str.split(" ", 3)); //["abc", "def", "ghi"]
11. 对象方括号的使用(计算属性)
// 对象方括号的使用(计算属性)
// 点符号要求key是有效的变量标识符,不能包含空格, 不能以数字开头, 也不能包含某些特殊字符, 这个时候我们可以使用[]
// 这个时候我们可以使用[]来定义计算属性, 我们甚至可以在[]中书写表达式:
var message = "Hello World";
var info = {
name: "zgc",
age: 29,
"my friend": "wf",
"paly games": function () {
console.log("games");
},
[message]: "你好, 世界",
};
console.log(info.name === info["name"]); //true
console.log(info["my friend"]); // wf
info["paly games"](); // games
var play = "paly games";
info[play](); // games
console.log(info[message]); // 你好, 世界
var foo = {
[info.name]() {
console.log("info.name", info.name);
},
};
foo["zgc"](); // info.name zgc
foo[info.name](); // info.name zgc
5. JavaScript数组
在 JavaScript 中,数组不是基本类型,而是具有以下核心特征的 Array 对象:
- JavaScript 数组是可调整大小的,并且可以包含不同的数据类型(当不需要这些特征时,可以使用类型化数组)
- JavaScript 数组不是关联数组,因此,不能使用任意字符串作为索引访问数组元素,但必须使用非负整数(或它们各自的字符串形式)作为索引访问。
- JavaScript 数组的索引从 0 开始:数组的第一个元素在索引
0处,第二个在索引1处,以此类推,最后一个元素是数组的length属性减去1的值。 - JavaScript 数组复制操作创建浅拷贝(所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝)
1. 数组的创建方式
// 1. 字面量创建(推荐)
var names = ["zgc", "wf ", "wlc", "cx"];
var products = [
{ name: "鼠标", price: 98 },
{ name: "键盘", price: 198 },
{ name: "显示器", price: 1988 },
];
console.log(names[1], names["1"]); // wf wf
// 2. 构造函数创建
var arr1 = new Array();
console.log(arr1); // []
var arr2 = new Array("abc", "cba", "cab");
console.log(arr2); // ['abc', 'cba', 'cab']
// 传入一个数字, 他会默认当成我们要创建一个对应长度的数组, 数组中的每一项内容为undefined
var arr3 = new Array(5);
console.log(arr3, arr3[0]); // [empty × 5] undefined
2. 数组的增删改查
// 1. 访问数组的元素
var names = ["zgc", "wf ", "wlc", "cx"];
console.log(names[2], names["2"]); // wlc wlc
// arr.at(i): i > 0时,. 则与arr[i]完全相同, i < 0 时, 则从数组的尾部向前数
console.log(names.at(0)); // zgc
console.log(names.at(-1)); // cx
// 2. 修改数组的元素
names[0] = "zqy";
console.log(names); // ['zqy', 'wf ', 'wlc', 'cx']
// 3. 新增数组的元素
// push(): push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度
let a1 = [1, 2, 3];
let item1 = a1.push("末尾1", "末尾2"); // 5
console.log(a1); // [1,2,3,末尾1",'末尾2']
// unshift(): unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度
let a2 = [1, 2, 3];
let item2 = a2.unshift("开头"); // 4
console.log(a2); // ['开头',1,2,3]
// 4. 删除数组的元素
// shift(): shift()方法删除数组的第一个元素,并返回这个元素
let a3 = [1, 2, 3];
let item3 = a3.shift(); // 1
console.log(a3); // [2,3]
// pop(): pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素
let a4 = [1, 2, 3];
let item4 = a4.pop(); // 3
console.log(a4); // [1,2]
// 5. splice(): 添加/删除/替换数组元素, 原数组会被改变
// array.splice(index, howmany, item1,.....,itemX )
// index:必需, 整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置
// howmany:可选, 要删除的项目数量。如果设置为 0,则不会删除项目
// item1, ..., itemX: 可选。向数组添加的新项目。
// 如果有元素被删除,返回包含被删除项目的新数组
var list = [1, 2, 3, 4, 5, 6, 7];
var newList = list.splice(0, 2);
console.log(newList, list); // [1, 2] [3, 4, 5, 6, 7]
var list = [1, 2, 3, 4, 5, 6, 7];
var newList = list.splice(2, 1);
console.log(newList, list); // [3] [1, 2, 4, 5, 6, 7]
var list = [1, 2, 3, 4, 5, 6, 7];
var newList = list.splice(2, 1, "zgc", "wf");
console.log(newList, list); // [3] [1, 2, 'zgc', 'wf', 4, 5, 6, 7]
// 添加是在开始的元素前面添加的
// [] 没有删除元素,返回空数组
var list = [1, 2, 3, 4, 5, 6, 7];
var newList = list.splice(2, 0, "zgc", "wf");
console.log(newList, list); // [] [1, 2, 'zgc', 'wf', 3, 4, 5, 6, 7]
// 数组如果元素不够,会删除到最后一个元素为止
var list = [1, 2, 3, 4, 5, 6, 7];
var newList = list.splice(-1, 5);
console.log(newList, list); // [7] [1, 2, 3, 4, 5, 6]
3. 数组的长度与遍历
var names = ["zgc", "wf", "wlc", "cx"];
// 1. 查看数组长度
console.log(names.length); // 4
// 2. 数组的长度是可写的, 即可以给数组手动扩容
names.length = 8;
console.log(names); // ['zgc', 'wf', 'wlc', 'cx', 空属性 × 4]
// 3. 如果设置的长度小于数组的原长度, 那么数组会发生截取
// 所以如果想要清空数组, 可以设置 arr.length = 0
names.length = 2;
console.log(names); // ['zgc', 'wf']
// 4. 数组的遍历
var arr = ["zgc", "wf", "wlc", "cx"];
// 4.1 for循环
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]); // "zgc" "wf" "wlc" "cx"
}
// 4.2 for...in
for (var index in arr) {
console.log(index); // 0 1 2 3
}
// 4.3 for...of
for (var item of arr) {
console.log(item); // // "zgc" "wf" "wlc" "cx"
}
// 4.4 for...in 与 for...of的区别
// 遍历数组:
let test = ["a", "b", "c"];
Array.prototype.ufo = "张三";
for (let item in test) {
console.log(item);
}
// 输出 0 1 2 ufo
// for…in 遍历数组会返回数组中所有可枚举的属性名, 包括原型链上可枚举的属性(下标值);
for (let item of test) {
console.log(item);
}
// 输出 a b c
// for…of 遍历数组返回数组的下标对应的属性值;
// 遍历对象:
const info = {
a: 1,
b: 2,
c: 3,
};
info.__proto__.username = "zgc";
for (let i in info) {
console.log(i);
}
// 输出: a b c username
// for…in 遍历对象会返回对象的所有可枚举的键名
// for (let i of info) {
// console.log(i);
// }
// for…of 遍历对象报错;
4. slice & concat & join
// 1. array.slice(begin, end); 截取数组的元素, 取值范围: [begin, end)
// begin(可选): 索引数值, 接受负值,从该索引处开始提取原数组中的元素
// end(可选):索引数值(不包括), 接受负值,在该索引处前结束提取原数组元素
var names = ["zgc", "wf", "wlc", "cx"];
// 不改变原数组, 返回一个新的数组
console.log(names.slice(2, 3), names); // ['wlc'] ['zgc', 'wf', 'wlc', 'cx']
// 可以用来浅拷贝数组
console.log(names.slice(), names); // ['zgc', 'wf', 'wlc', 'cx'] (4) ['zgc', 'wf', 'wlc', 'cx']
/*
tips:
新数组是浅拷贝的,且元素是简单数据类型,改变之后新旧数组不会互相干扰
如果数组元素是复杂数据类型(对象,数组)的话,改变其中一个的值,另外一个也会改变
*/
let test = [{ name: "OBKoro1" }];
let clone = test.slice();
console.log(test, clone); // [{"name":"OBKoro1"}] [{"name":"OBKoro1"}]
test[0].name='改变原数组';
console.log(test, clone); // [{"name":"改变原数组"}] [{"name":"改变原数组"}]
clone[0].name='改变拷贝数组',clone[0].koro='改变拷贝数组';
console.log(test, clone);
// [{"name":"改变拷贝数组","koro":"改变拷贝数组"}] [{"name":"改变拷贝数组","koro":"改变拷贝数组"}]
// 2. concat方法用于合并两个或多个数组,不改变原数组, 返回一个新数组
let a = [1, 2, 3];
let b = [4, 5, 6];
//连接两个数组
let newVal = a.concat(b); // [1,2,3,4,5,6]
// 连接三个数组
let c = [7, 8, 9];
let newVal2 = a.concat(b, c); // [1,2,3,4,5,6,7,8,9]
// 添加元素
let newVal3 = a.concat("添加元素", b, c, "再加一个");
// [1,2,3,"添加元素",4,5,6,7,8,9,"再加一个"]
// 合并嵌套数组 会浅拷贝嵌套数组
let d = [1, 2];
let f = [3, [4]];
let newVal4 = d.concat(f); // [1,2,3,[4]]
// 3. join方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串(不改变原数组)
let list = ["hello", "world"];
let str1 = list.join(); // 'hello,world'
let str2 = list.join(" "); // 'hello world'
let str3 = list.join("+"); // 'hello+world'
console.log(str1, str2, str3);
5. 查找数组中的元素(find & findIndex & indexOf)
// 1. 数组中存放的是原始类型
var names = ["zgc", "wf", "wlc", "cx"];
// indexOf(): 查找数组是否存在某个元素,返回在数组中可以找到给定元素的第一个索引,如果不存在,则返回-1
// 数组的indexOf搜索跟字符串的indexOf不一样,数组的indexOf使用严格相等===搜索元素,即数组元素要完全匹配才能搜索成功。
// indexOf()不能识别NaN
console.log(names.indexOf("wf")); // 1
// 2. 数组中存放的是复杂类型
var products = [
{ name: "鼠标", price: 98 },
{ name: "键盘", price: 198 },
{ name: "显示器", price: 1988 },
];
// 2.1 for
var pro1 = null;
for (var i = 0; i < products.length; i++) {
if (products[i].name === "键盘") {
pro1 = products[i];
break;
}
}
console.log("pro1", pro1); // {name: '键盘', price: 198}
// 2.2 find: 高阶函数
// 用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。
var pro2 = products.find((item, index, arr) => {
// console.log(item, index, arr); // 数组当前元素的值, 当前元素的索引值, 数组对象本身
// if(item.name === "键盘") return true
return item.name === "键盘";
});
console.log("pro2", pro2);
// 2.3 findIndex: 高阶函数
// 返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
var pro3 = products.findIndex((item, index, arr) => item.name === "显示器");
console.log("pro3", pro3); // 2
6. forEach的使用&手写forEach
// 1. forEach: 遍历数组, 按升序为数组中含有效值的每一项执行一次回调函数
// arr.forEach(function (currentValue, currentIndex, arr) {}, thisArg);
//currentValue 必需。当前元素
//currentIndex 可选。当前元素的索引
//arr 可选。当前元素所属的数组对象。
//thisArg 可选参数。当执行回调函数时,用作 this 的值。
// 2. forEach注意事项:
let a = [1, 2, , 3]; // 最后下标第二个元素是空的,不会遍历(undefined、null会遍历)
let obj = { name: "OBKoro1" };
let result = a.forEach(function (value, index, array) {
a[3] = "改变元素";
a.push("添加到尾端,不会被遍历");
// console.log(value, "forEach传递的第一个参数", index, array, array.length);
console.log(this.name); // 默认this是window,这里this绑定在obj对象上,所以OBKoro1 打印三次
// break; // break会报错
return value; // return只能结束本次回调 会执行下次回调
console.log("不会执行, 因为return 会执行下一次循环回调");
}, obj);
console.log("result", result); // 即使return了一个值,也还是返回undefined
// 1 'forEach传递的第一个参数' 0 [1, 2, 空白, '改变元素', '添加到尾端,不会被遍历'] 5
// OBKoro1
// 2 'forEach传递的第一个参数' 1 [1, 2, 空白, '改变元素', '添加到尾端,不会被遍历', '添加到尾端,不会被遍历'] 6
// OBKoro1
// 改变元素 forEach传递的第一个参数 3 (7) [1, 2, 空白, '改变元素', '添加到尾端,不会被遍历', '添加到尾端,不会被遍历', '添加到尾端,不会被遍历'] 7
// OBKoro1
// result undefined
/*
1. 无法中途退出循环,只能用return退出本次回调,进行下一次回调。
2. 它总是返回 undefined值,即使你return了一个值。
3. 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
4. 遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历
5. 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值
*/
// 2. 手写forEach
var products = [
{ name: "鼠标", price: 98 },
{ name: "键盘", price: 198 },
{ name: "显示器", price: 1988 },
];
var test = {
name: "zgc",
};
Array.prototype._forEach = function (fn, ins) {
if (typeof fn !== "function") throw Error("回调函数不是一个函数~");
for (let i = 0; i < this.length; i++) {
fn.call(ins, this[i], i, this);
}
};
products._forEach(function (item, index, arr) {
console.log(item, index, arr, this);
}, test); // 当指定this值时, 不要使用箭头函数的书写方式
7. 手写find&findIndex方法
// array.find(function(item, index, arr), ins)
// 回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined
// array.findIndex(function(item, index, arr), ins)
// 返回符合测试条件的第一个数组元素索引,如果没有符合条件的则返回 -1
// 参数1:回调函数(必需)
// 参数2:传递给回调函数的this指针(可选), 如果这个参数为空,this指针为window
// 注:当回调函数是箭头函数时,参数2无效,始终指向全局window变量
var products = [
{ name: "鼠标", price: 98 },
{ name: "键盘", price: 198 },
{ name: "显示器", price: 1988 },
];
var test = {
name: "zgc",
};
Array.prototype._find = function (fn, ins) {
let res;
for (let i = 0; i < this.length; i++) {
if (fn.call(ins, this[i], i, this)) {
res = this[i];
break;
}
}
return res;
};
Array.prototype._findIndex = function (fn, ins) {
let res = -1;
for (let i = 0; i < this.length; i++) {
if (fn.call(ins, this[i], i, this)) {
res = i;
break;
}
}
return res;
};
var pro1 = products._find(function (item, index, arr) {
// console.log(item, index, arr, this);
return item.name === "键盘";
}, test);
console.log(pro1); // {name: '键盘', price: 198}
var pro2 = products._findIndex(function (item, index, arr) {
// console.log(item, index, arr, this);
return item.name === "键盘";
}, test);
console.log(pro2); // 1
8. filter & map & reduce
- filter & map & reduce 都不会改变原数组
// 1. filter: 过滤原始数组,返回一个新数组, 其包含通过所提供函数实现的测试的所有元素
// let new_array = arr.filter(function(item, index, arr), thisArg)
// 参数1:回调函数(必需)
// 参数2:传递给回调函数的this指针(可选), 如果这个参数为空,this指针为window
// 回调函数的参数
// 1. item(必须),数组当前元素的值
// 2. index(可选), 当前元素的索引值
// 3. arr(可选),数组对象本身
var products = [
{ name: "鼠标", price: 98 },
{ name: "键盘", price: 198 },
{ name: "显示器", price: 1988 },
];
// var new_array = products.filter(
// (item, index, arr) => item.name !== "键盘"
// );
// console.log(new_array);
// [ { name: "鼠标", price: 98 }, { name: "显示器", price: 1988 } ];
// 手写filter方法
Array.prototype._filter = function (fn, ins) {
let result = [];
for (let i = 0; i < this.length; i++) {
if (fn.call(ins, this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
var new_array = products._filter((item) => item.name !== "键盘");
console.log(new_array);
// 2. map 对数组中的每个元素进行处理,返回新的数组,其结果是原数组中的每个元素都调用提供的方法后返回的结果
// let new_array = arr.map(function(item, index, arr), thisArg) // 同 filter
var map_array = products.map((item) => {
return { ...item, tag: "数码" };
});
console.log(map_array);
// [
// { name: "鼠标", price: 98, tag: "数码" },
// { name: "键盘", price: 198, tag: "数码" },
// { name: "显示器", price: 1988, tag: "数码" },
// ];
// 3. reduce 为数组提供累加器,合并为一个值
// array.reduce(function(total, item, index, arr), initialValue)
// 参数1:回调函数(必需)
// 参数2:指定第一次回调 的第一个参数, 即total的初始值 (可选)
/*
如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 item 等于数组中的第一个值;
如果 initialValue 未被提供,那么 total 等于数组中的第一个值,item 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError;
如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回;
*/
// 回调函数的参数
// 1. total(必须),初始值, 或者上一次调用回调返回的值
// 2. item(必须),数组当前元素的值
// 3. index(可选), 当前元素的索引值
// 4. arr(可选),数组对象本身
// 数组求和
var nums = [1, 2, 3, 4, 5];
// 第一次执行: total -> 0 , item -> 1
// 第二次执行: total -> 1 , item -> 2
// 第三次执行: total -> 3 , item -> 3
// 第四次执行: total -> 6 , item -> 4
// 第五次执行: total -> 10 , item -> 5
var new_nums = nums.reduce((total, item) => {
return total + item;
}, 0);
// 第一次执行: total -> 1 , item -> 2
// 第二次执行: total -> 3 , item -> 3
// 第三次执行: total -> 6 , item -> 4
// 第四次执行: total -> 10 , item -> 5
var new_nums2 = nums.reduce((total, item) => {
return total + item;
});
console.log(new_nums, new_nums2); // 15 15
// 将二维数组转化为一维 将数组元素展开
const arrs = [
[0, 1],
[2, 3],
[4, 5],
];
// 第一次执行: total -> [] , item -> [0, 1]
// 第二次执行: total -> [0, 1] , item -> [2, 3]
// 第三次执行: total -> [0, 1, 2, 3], item -> [4, 5]
let flattened = arrs.reduce((total, item) => total.concat(item), []);
// [0, 1, 2, 3, 4, 5]
// 练习1: 计算总价格
var products = [
{ name: "鼠标", price: 100, count: 3 },
{ name: "键盘", price: 200, count: 5 },
{ name: "显示器", price: 2000, count: 1 },
];
var totalPrice = products.reduce((total, item) => {
return total + item.price * item.count;
}, 0);
console.log(totalPrice); // 3300
// 练习2: 得到所有的偶数, 映射所有偶数的平方, 并计算它们的和
var list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var total = list
.filter((item) => item % 2 === 0)
.map((item) => item * item)
.reduce((total, item) => total + item, 0);
console.log(total);
9. Date
// Date 对象用于处理日期和时间。
// 实例化日期有四种方式:
// var d = new Date();
// var d = new Date(milliseconds);
// var d = new Date(dateString);
// var d = new Date(year, month, day, hours, minutes, seconds, milliseconds);
// 1. 没有任何传入对象, 截取到当前时间
var d1 = new Date();
console.log("d1", d1); // Tue Feb 14 2023 11:58:21 GMT+0800
// 2. 传递从1970年1月1日0点的毫秒数,根据传递的这个毫秒数来创建日期对象(时间戳)
var d2 = new Date(10078273200453);
console.log("d2", d2); // Wed May 15 2289 00:20:00 GMT+0800
// 3. 传入参数: 时间字符串, 根据日期字符串创建一个新的日期对象
var d3 = new Date("2022-08-15");
console.log("d3", d3); // Mon Aug 15 2022 08:00:00 GMT+0800
// 4. 用指定日期和时间创建新的日期对象, 传入具体的年月日时分秒毫秒
// 7个数字分别指定年、月、日、小时、分钟、秒和毫秒(按此顺序);
// JavaScript 从 0 到 11 计算月份, 一月是 0。十二月是11;
var d4 = new Date(2033, 10, 12, 09, 08, 07, 333);
console.log("d4", d4); // Sat Nov 12 2033 09:08:07 GMT+0800: 周六 11月 12日 2033年9点8分7秒
// 1. getFullYear() 返回四位数年份
console.log(new Date().getFullYear()); // 2023
// 2. getMonth() 返回值0(一月) 到 11(十二月) 之间的一个整数
console.log(new Date().getMonth()); // 1 : (2月)
// 3. getDate() 返回一个月中的第几天(1 ~ 31)
console.log(new Date().getDate()); // 14
// 4. getDay() 返回星期,一周中的第几天(0 ~ 6,礼拜天是0)
console.log(new Date().getDay()); // 2
// 5. getHours() 返回小时 (0 ~ 23)
console.log(new Date().getHours()); // 16
// 6. getMinutes() 返回分钟 (0 ~ 59)
console.log(new Date().getMinutes()); // 42
// 7. getSeconds() 返回秒数 (0 ~ 59)
console.log(new Date().getSeconds()); // 49
// 8. getMilliseconds() 返回毫秒(0 ~ 999)
console.log(new Date().getMilliseconds()); // 334
// 9. getTime() 根据本地时返回 1970 年 1 月 1 日至今的毫秒数
console.log(new Date().getTime()); // 1676364191291
// 1. 获取Date的时间戳
// 方法1: 获取当前时间的时间戳
var timesTamp1 = Date.now();
console.log("timesTamp1", timesTamp1);
// 方法2: 将date对象转换成时间戳
var timesTamp2 = d2.getTime();
var timesTamp3 = d2.valueOf();
console.log("timesTamp2", timesTamp2); // 10078273200453
console.log("timesTamp3", timesTamp3); // 10078273200453
// 方法3: Date.parse: 解析日期字符串,并返回日期字符串与 1970 年 1 月 1 日午夜之间的毫秒数(时间戳)
var timeStr = "2033-03-23";
console.log(Date.parse(timeStr));
// 等同于 new Date(timeStr).getTime()
// 建议输入格式: YYYY-MM-DDTHH:MM:SS.sssZ(年月日T时分秒毫秒), 日期和时间通过大写字母 T 来分隔
// 如果输入的格式不能被解析, 那么会返回NaN