无序言,本文开门见山,直接讲解
Javascript
作为前端三剑客中最重要的一环,它是一种由Netscape
的LiveScript
发展而来的原型化继承的面向对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如Perl
,遗留的速度问题,为客户提供更流畅的浏览效果。
提示:以下是本篇文章正文内容,下面案例可供参考
使用方式:
HTML页面中的任意位置加上标签即可。
常见使用方式有以下几种:
标签内写JS代码。
。import
关键字引入到当前作用域。例如:
/static/js/index.js
文件中的内容为:
let name = "xiaozhuangzzz";
function print() {
console.log("Hello World!");
}
export {
name,
print
}
中的内容为:
<script type="module">
import { name, print } from "/static/js/index.js";
console.log(name);
print();
</script>
执行顺序
1.类似于HTML
与CSS
,按从上到下的顺序执行;
2.事件驱动执行;
HTML
, CSS
, JavaScript
三者之间的关系
1.CSS
控制HTML
2.JavaScript
控制HTML
与CSS
3.为了方便开发与维护,尽量按照上述顺序写代码。例如:不要在HTML中调用JavaScript
中的函数。
let
与const
用来声明变量,作用范围为当前作用域。
let
用来定义变量;const
用来定义常量;例如:
let s = "zzz, x = 5;
let d = {
name: "xiaozhuang",
age: 19,
}
const n = 100;
变量类型
number
:数值变量,例如1
, 2.5
string
:字符串,例如"zzz"
, 'xiaozhuang'
,单引号与双引号均可。字符串中的每个字符为只读类型。boolean
:布尔值,例如true
, false
object
:对象,类似于C++中的指针,例如[1, 2, 3]
,{name: "yxc", age: 18}
,null
undefined
:未定义的变量类似于Python,JavaScript中的变量类型可以动态变化。
运算符
与C++、Python、Java类似,不同点:
**
表示乘方等于与不等于用===
和!==
输入
input
、textarea
等标签获取用户的键盘输入,通过click
、hover
等事件获取用户的鼠标输入。Ajax
与WebSocket
从服务器端获取输入let fs = require('fs');
let buf = '';
process.stdin.on('readable', function() {
let chunk = process.stdin.read();
if (chunk) buf += chunk.toString();
});
process.stdin.on('end', function() {
buf.split('\n').forEach(function(line) {
let tokens = line.split(' ').map(function(x) { return parseInt(x); });
if (tokens.length != 2) return;
console.log(tokens.reduce(function(a, b) { return a + b; }));
});
});
输出
console.log
,会将信息输出到浏览器控制台Ajax
与WebSocket
将结果返回到服务器格式化字符串
let name = 'xiaozhuang', age = 19;
let s = `My name is ${name}, I'm ${age} years old.`;
let s =
`
标题
段落
/div>`
- 保留两位小数如何输出
let x = 1.234567;
let s = `${x.toFixed(2)}`;
练习
- 输出Hello World。
- 输入两个数,计算两个数的和。
- 输入一个小数,返回向零取整之后的结果。
- 输入a, b, c,输出 (a + b) * c 的值。
- 求反三位数。
- 输出如下的菱形。
*
***
*****
***
*
4.判断语句
第三节习题答案解析3(1,2应该能独立完成吧):
function main() {
run.addEventListener("click", function () {
let x = parseFloat(input.value);
output.innerHTML = parseInt(x);
});
}
第三节习题答案解析4:
function main() {
run.addEventListener("click", function () {
let [a, b, c] = input.value.split(' ');
a = parseFloat(a), b = parseFloat(b), c = parseFloat(c);
output.innerHTML = (a + b) * c;
});
}
第三节习题答案解析5:
function main() {
run.addEventListener("click", function () {
let s = parseInt(input.value);
let t = '';
t += s % 10;
s = parseInt(s / 10);
t += s % 10;
s = parseInt(s / 10);
t += s;
output.innerHTML = t;
});
}
正文:
JavaScript中的if-else
语句与C++
、Python
、Java
中类似。
例如:
let score = 90;
if (score >= 85) {
console.log("A");
} else if (score >= 70) {
console.log("B");
} else if (score >= 60) {
console.log("C");
} else {
console.log("D");
}
JavaScript中的逻辑运算符也与C++
、Java
中类似:
&&
表示与
||
表示或
!
表示非
练习
1.输入一个年份,如果是闰年输出yes,否则输出no。
2.输入三个数,输出三个数中的最大值。
<习题解析下节更新>
5.循环语句
第四节习题1:
function main() {
run.addEventListener("click", function () {
let year = input.value;
if(year % 400 === 0 || year % 100 !== 0 && year % 4 === 0){
output.innerHTML = "yes";
} else {
output.innerHTML = "no";
}
});
}
第四节习题2:
function main() {
run.addEventListener("click", function () {
let [a, b, c] = input.value.split(' ');
a = parseInt(a), b = parseInt(b), c = parseInt(c);
output.innerHTML = Math.max(a, b, c);
});
}
JavaScript
中的循环语句与C++
中类似,也包含for
、while
、do while
循环。
for
循环
for (let i = 0; i < 10; i++) {
console.log(i);
}
枚举对象或数组时可以使用:
for-in
循环,可以枚举数组中的下标,以及对象中的key
for-of
循环,可以枚举数组中的值,以及对象中的value
- –
while
循环
let i = 0;
while (i < 10) {
console.log(i);
i++;
}
do while
循环
do while
语句与while
语句非常相似。唯一的区别是,do while
语句限制性循环体后检查条件。不管条件的值如何,我们都要至少执行一次循环。
let i = 0;
do {
console.log(i);
i++;
} while (i < 10);
练习
- 求1~100中所有数的立方和。
- 求斐波那契数列的第n项。f(1) = 1, f(2) = 1, f(3) = 2, f(n) = f(n-1) + f(n-2)。
- 打印1~100中的所有质数。
6.对象
第五节课后难题解析3:
for (let i = 2; i <= 100; i++) {
let flag = true;
for (let j = 2; j * j <= i; j++)
if (i % j === 0) {
flag = false;
break;
}
if (flag === true)
console.log(i);
}
英文名称:Object
。
类似于C++
中的map
,由key:value
对构成。
value
可以是变量、数组、对象、函数等。
- 函数定义中的
this
用来引用该函数的“拥有者”。
例如:
let person = {
name: "xiaozhuang",
age: 19,
money: 0,
add_money: function (x) {
this.money += x;
}
}
对象属性与函数的调用方式:
person.name
、person.add_money()
person["name"]
、person["add_money"]()
7.函数
函数是用对象来实现的。
函数也跟C++
中的函数类似。
定义方式
function add(a, b) {
return a + b;
}
let add = function (a, b) {
return a + b;
}
let add = (a, b) => {
return a + b;
}
返回值
如果未定义返回值,则返回undefined
。
9.类
与C++
中的Class
类似。但是不存在私有成员。
this
指向类的实例。
定义
class Point {
constructor(x, y) { // 构造函数
this.x = x; // 成员变量
this.y = y;
this.init();
}
init() {
this.sum = this.x + this.y; // 成员变量可以在任意的成员函数中定义
}
toString() { // 成员函数
return '(' + this.x + ', ' + this.y + ')';
}
}
let p = new Point(3, 4);
console.log(p.toString());
继承
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 这里的super表示父类的构造函数
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
注意:
super
这个关键字,既可以当作函数使用,也可以当作对象使用。
–super
作为函数调用时,代表父类的构造函数,且只能用在子类的构造函数之中。
–super
作为对象时,指向父类的原型对象。
- 在子类的构造函数中,只有调用
super
之后,才可以使用this
关键字。
- 成员重名时,子类的成员会覆盖父类的成员。类似于
C++
中的多态。
静态方法
在成员函数前添加static
关键字即可。静态方法不会被类的实例继承,只能通过类来调用。例如:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
static print_class_name() {
console.log("Point");
}
}
let p = new Point(1, 2);
Point.print_class_name();
p.print_class_name(); // 会报错
静态变量
在ES6中,只能通过class.propname
定义和访问。例如
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
Point.cnt++;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
Point.cnt = 0;
let p = new Point(1, 2);
let q = new Point(3, 4);
console.log(Point.cnt);
10.事件
超链接:JS事件详解补充2.0
JavaScript的代码一般通过事件
触发。
可以通过addEventListener
函数为元素绑定事件的触发函数。
常见的触发函数有:
鼠标:
click
:鼠标左键点击
dblclick
:鼠标左键双击
contextmenu
:鼠标右键点击
mousedown
:鼠标按下,包括左键、滚轮、右键
event.button
:0表示左键,1表示中键,2表示右键
mouseup
:鼠标弹起,包括左键、滚轮、右键
event.button
:0表示左键,1表示中键,2表示右键
键盘:
keydown
:某个键是否被按住,事件会连续触发
event.code
:返回按的是哪个键
event.altKey
、event.ctrlKey
、event.shiftKey
分别表示是否同时按下了alt
、ctrl
、shift
键。
keyup
:某个按键是否被释放
event
常用属性同上
keypress
:紧跟在keydown事件后触发,只有按下字符键时触发。适用于判定用户输入的字符。
event
常用属性同上
keydown
、keyup
、keypress
的关系类似于鼠标的mousedown
、mouseup
、click
表单:
focus
:聚焦某个元素
blur
:取消聚焦某个元素
change
:某个元素的内容发生了改变
窗口:
需要作用到window
元素上。
resize
:当窗口大小放生变化
scroll
:滚动指定的元素
load
:当元素被加载完成
二、Javascipt常用库
1.JQuery
使用方式
- 在
元素中添加:
- 按
jQuery
官网提示下载
选择器
$(selector)
,例如:
$('div');
$('.big-div');
$('div > p')
selector
类似于CSS选择器。
事件
$(selector).on(event, func)
绑定事件,例如:
$('div').on('click', function (e) {
console.log("click div");
})
$(selector).off(event, fun)
删除事件,例如:
$('div').on('click', function (e) {
console.log("click div");
$('div').off('click');
});
当存在多个相同类型的事件触发函数时,可以通过click.name
来区分,例如:
$('div').on('click.first', function (e) {
console.log("click div");
$('div').off('click.first');
});
在事件触发的函数中的return false
等价于同时执行:
e.stopPropagation()
:阻止事件向上传递
e.preventDefault()
:阻止事件的默认行为
元素的隐藏、展现
$A.hide()
:隐藏,可以添加参数,表示消失时间
$A.show()
:展现,可以添加参数,表示出现时间
$A.fadeOut()
:慢慢消失,可以添加参数,表示消失时间
$A.fadeIn()
:慢慢出现,可以添加参数,表示出现时间
元素的添加、删除
$('Hello World')
:构造一个jQuery对象
$A.append($B)
:将 B 添加到 B添加到 B添加到A的末尾
$A.prepend($B)
:将 B 添加到 B添加到 B添加到A的开头
$A.remove()
:删除元素$A
$A.empty()
:清空元素$A的所有儿子
对类的操作
$A.addClass(class_name)
:添加某个类
$A.removeClass(class_name)
:删除某个类
$A.hasClass(class_name)
:判断某个类是否存在
对CSS的操作
$("div").css("background-color")
:获取某个CSS的属性
$("div").css("background-color","yellow")
:设置某个CSS的属性
同时设置多个CSS的属性:
$('div').css({
width: "200px",
height: "200px",
"background-color": "orange",
});
对标签属性的操作
$('div').attr('id')
:获取属性
$('div').attr('id', 'ID')
:设置属性
对HTML内容、文本的操作
不需要背每个标签该用哪种,用到的时候Google或者百度即可。
$A.html()
:获取、修改HTML内容
$A.text()
:获取、修改文本信息
$A.val()
:获取、修改文本的值
查找
$(selector).parent(filter)
:查找父元素
$(selector).parents(filter)
:查找所有祖先元素
$(selector).children(filter)
:在所有子元素中查找
$(selector).find(filter)
:在所有后代元素中查找
ajax
GET方法:
$.ajax({
url: url,
type: "GET",
data: {
},
dataType: "json",
success: function (resp) {
},
});
POST方法:
$.ajax({
url: url,
type: "POST",
data: {
},
dataType: "json",
success: function (resp) {
},
});
2.setTimeout与setInterval
setTimeout(func, delay)
delay
毫秒后,执行函数func()
clearTimeout()
关闭定时器,例如:
let timeout_id = setTimeout(() => {
console.log("Hello World!")
}, 2000); // 2秒后在控制台输出"Hello World"
clearTimeout(timeout_id); // 清除定时器
setInterval(func, delay)
每隔delay
毫秒,执行一次函数func()
。
第一次在第delay
毫秒后执行。
clearInterval()
关闭周期执行的函数,例如:
let interval_id = setInterval(() => {
console.log("Hello World!")
}, 2000); // 每隔2秒,输出一次"Hello World"
clearInterval(interval_id); // 清除周期执行的函数
3.requestAnimationFrame
requestAnimationFrame(func)
该函数会在下次浏览器刷新页面之前执行一次,通常会用递归写法使其每秒执行60次func
函数。调用时会传入一个参数,表示函数执行的时间戳,单位为毫秒。
例如:
let step = (timestamp) => { // 每帧将div的宽度增加1像素
let div = document.querySelector('div');
div.style.width = div.clientWidth + 1 + 'px';
requestAnimationFrame(step);
};
requestAnimationFrame(step);
与setTimeout
和setInterval
的区别:
requestAnimationFrame
渲染动画的效果更好,性能更佳。 该函数可以保证每两次调用之间的时间间隔相同,但setTimeout
与setInterval
不能保证这点。setTmeout
两次调用之间的间隔包含回调函数的执行时间;setInterval
只能保证按固定时间间隔将回调函数压入栈中,但具体的执行时间间隔仍然受回调函数的执行时间影响。
- 当页面在后台时,因为页面不再渲染,因此
requestAnimationFrame
不再执行。但setTimeout与setInterval
函数会继续执行。
4.Map与Set
Map
Map
对象保存键值对。
- 用
for...of
或者forEach
可以按插入顺序遍历。
- 键值可以为任意值,包括函数、对象或任意基本类型。
常用API:
set(key, value)
:插入键值对,如果key
已存在,则会覆盖原有的value
get(key)
:查找关键字,如果不存在,返回undefined
size
:返回键值对数量
has(key)
:返回是否包含关键字key
delete(key)
:删除关键字key
clear()
:删除所有元素
5.LocalStorage
可以在用户的浏览器上存储键值对。
常用API:
setItem(key, value)
:插入
getItem(key)
:查找
removeItem(key)
:删除
clear()
:清空
6.JSON
JSON对象用于序列化对象
、数组
、数值
、字符串
、布尔值
和null
。
常用API
:
JSON.parse()
:将json字符串解析成js对象
JSON.stringify()
:将js对象转化为json字符串
7.Date日期
返回值为整数
的API,数值为
1970-1-1 00:00:00 UTC(世界标准时间)到某个时刻所经过的毫秒数
:
Date.now()
:返回现在时刻。
Date.parse("2022-04-15T15:30:00.000+08:00")
:返回北京时间2022年4月15日
15:30:00的时刻。
与Date
对象的实例相关的API
:
new Date()
:返回现在时刻。
new Date("2022-04-15T15:30:00.000+08:00")
:返回北京时间2022年4月15日
15:30:00的时刻。
两个Date对象实例的差值为毫秒数
getDay()
:返回星期,0表示星期日,1-6表示星期一至星期六
getDate()
:返回日,数值为1-31
getMonth()
:返回月,数值为0-11
getFullYear()
:返回年份
getHours()
:返回小时
getMinutes()
:返回分钟
getSeconds()
:返回秒
getMilliseconds()
:返回毫秒
8.WebSocket网络通信
与服务器建立全双工连接
。
常用API
:
new WebSocket('ws://localhost:8080');
:建立ws连接。
send()
:向服务器端发送一个字符串。一般用JSON将传入的对象序列化为字符串。
onopen
:类似于onclick,当连接建立时触发。
onmessage
:当从服务器端接收到消息时触发。
close()
:关闭连接。
onclose
:当连接关闭后触发。
9.window
window.open("https://www.acwing.com")
:在新标签栏中打开页面
location.reload()
:刷新页面
location.href = "https://www.acwing.com"
:在当前标签栏中打开页面
10.Canvas库
Canvas教程
三、Javascript进阶(重点)
1.事件流(捕获和冒泡)
事件流
是对事件执行过程的描述
,了解事件的执行过程有助于加深对事件的理解,提升开发实践中对事件运用的灵活度。
如上图所示,任意事件被触发时总会经历两个阶段:【捕获阶段】
和【冒泡阶段】
。
简言之,捕获阶段是【从父到子】
的传导过程,冒泡阶段是【从子向父】
的传导过程。
捕获和冒泡:
<body>
<h3>事件流</h3>
<p>事件流是事件在执行时的底层机制,主要体现在父子盒子之间事件的执行上。</p>
<div class="outer">
<div class="inner">
<div class="child"></div>
</div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer');
const inner = document.querySelector('.inner');
const child = document.querySelector('.child');
// html 元素添加事件
document.documentElement.addEventListener('click', function () {
console.log('html...')
})
// body 元素添加事件
document.body.addEventListener('click', function () {
console.log('body...')
})
// 外层的盒子添加事件
outer.addEventListener('click', function () {
console.log('outer...')
})
// 中间的盒子添加事件
outer.addEventListener('click', function () {
console.log('inner...')
})
// 内层的盒子添加事件
outer.addEventListener('click', function () {
console.log('child...')
})
</script>
</body>
执行上述代码后发现,当单击事件触发时
,其祖先元素的单击事件也【相继触发】
,这是为什么呢?
结合事件流的特征,我们知道当某个元素的事件被触发时,事件总是会先经过其祖先
才能到达当前元素,然后再由当前元素向祖先传递
,事件在流动的过程中遇到相同的事件便会被触发。
再来关注一个细节就是事件相继触发的【执行顺序】
,事件的执行顺序是可控制的,即可以在捕获阶段被执行,也可以在冒泡阶段被执行。
如果事件是在冒泡阶段执行的,我们称为冒泡模式,它会先执行子盒子事件再去执行父盒子事件
,默认是冒泡模式。
如果事件是在捕获阶段执行的,我们称为捕获模式,它会先执行父盒子事件再去执行子盒子事件
。
<body>
<h3>事件流</h3>
<p>事件流是事件在执行时的底层机制,主要体现在父子盒子之间事件的执行上。</p>
<div class="outer">
<div class="inner"></div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
}, true) // true 表示在捕获阶段执行事件
// 中间的盒子
outer.addEventListener('click', function () {
console.log('inner...')
}, true)
</script>
</body>
结论:
addEventListener
第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发
addEventListener
第3个参数为 true
表示捕获阶段触发,false
表示冒泡阶段触发,默认值为 false
- 事件流只会在父子元素具有
相同事件类型
时才会产生影响
- 绝大部分场景都采用默认的
冒泡模式
(其中一个原因是早期 IE 不支持捕获)
阻止冒泡:
<body>
<h3>阻止冒泡</h3>
<p>阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。</p>
<div class="outer">
<div class="inner">
<div class="child"></div>
</div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
const child = document.querySelector('.child')
// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
})
// 中间的盒子
inner.addEventListener('click', function (ev) {
console.log('inner...')
// 阻止事件冒泡
ev.stopPropagation()
})
// 内层的盒子
child.addEventListener('click', function (ev) {
console.log('child...')
// 借助事件对象,阻止事件向上冒泡
ev.stopPropagation()
})
</script>
</body>
2.事件委托
事件委托
是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率
。
大量的事件监听是比较耗费性能的,如下代码所示:
<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button');
for(let i = 0; i <= buttons.length; i++) {
// 为 10000 个 button 元素添加了事件
buttons.addEventListener('click', function () {
// 省略具体执行逻辑...
})
}
</script>
利用事件流的特征,可以对上述的代码进行优化,事件的的冒泡模式总是会将事件流向其父元素的,如果父元素监听了相同的事件类型,那么父元素的事件就会被触发并执行
,正是利用这一特征对上述代码进行优化,如下代码所示:
<script>
// 假设页面中有 10000 个 button 元素
let buttons = document.querySelectorAll('table button');
// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
let parents = document.querySelector('table');
parents.addEventListener('click', function () {
console.log('点击任意子元素都会触发事件...');
})
</script>
事件对象中的属性 target
或 srcElement
属性表示真正触发事件的元素,它是一个元素类型的节点。
<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button')
// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
const parents = document.querySelector('table')
parents.addEventListener('click', function (ev) {
// console.log(ev.target);
// 只有 button 元素才会真正去执行逻辑
if(ev.target.tagName === 'BUTTON') {
// 执行的逻辑
}
})
</script>
优化过的代码只对祖先元素添加事件监听
,相比对 10000 个元素添加事件监听执行效率要高许多!!!
3.BOM-Window对象
BOM (Browser Object Model ) 是浏览器对象模型
- window对象是一个
全局对象
,也可以说是JavaScript中的顶级对象
- 像
document、alert()、console.log()
这些都是window的属性,基本BOM的属性和方法都是window的
- 所有通过
var定义在全局作用域中的变量、函数
都会变成window对象的属性和方法
- window对象下的属性和方法调用的时候
可以省略window
location对象 :
location (地址)
它拆分并保存了 URL 地址
的各个组成部分, 它是一个对象
属性/方法
说明
href
属性,获取完整的 URL 地址,赋值时用于地址的跳转
search
属性,获取地址中携带的参数,符号 ?后面部分
hash
属性,获取地址中的啥希值,符号 # 后面部分
reload()
方法,用来刷新当前页面,传入参数 true 时表示强制刷新
<body>
<form>
<input type="text" name="search"> <button>搜索</button>
</form>
<a href="#/music">音乐</a>
<a href="#/download">下载</a>
<button class="reload">刷新页面</button>
<script>
// location 对象
// 1. href属性 (重点) 得到完整地址,赋值则是跳转到新地址
console.log(location.href)
// location.href = 'http://www.itcast.cn'
// 2. search属性 得到 ? 后面的地址
console.log(location.search) // ?search=笔记本
// 3. hash属性 得到 # 后面的地址
console.log(location.hash)
// 4. reload 方法 刷新页面
const btn = document.querySelector('.reload')
btn.addEventListener('click', function () {
// location.reload() // 页面刷新
location.reload(true) // 强制页面刷新 ctrl+f5
})
</script>
</body>
navigator对象:
navigator
是对象,该对象下记录了浏览器自身的相关信息
常用属性和方法:
- 通过
userAgent
检测浏览器的版本及平台
// 检测 userAgent(浏览器信息)
(function () {
const userAgent = navigator.userAgent
// 验证是否为Android或iPhone
const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
// 如果是Android或iPhone,则跳转至移动站点
if (android || iphone) {
location.href = 'http://m.itcast.cn'
}})();
histroy对象:
history
(历史)是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退等
使用场景
history
对象一般在实际开发中比较少用,但是会在一些OA 办公系统中见到。
常见方法:
<body>
<button class="back">←后退</button>
<button class="forward">前进→</button>
<script>
// histroy对象
// 1.前进
const forward = document.querySelector('.forward')
forward.addEventListener('click', function () {
// history.forward()
history.go(1)
})
// 2.后退
const back = document.querySelector('.back')
back.addEventListener('click', function () {
// history.back()
history.go(-1)
})
</script>
</body>
4.本地存储(重要)
好处:
- 1、页面刷新或者关闭不丢失数据,实现数据持久化
- 2、容量较大,
sessionStorage
和 localStorage
约 5M 左右
localStorage(重点):
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>本地存储-localstoragetitle>
head>
<body>
<script>
// 本地存储 - localstorage 存储的是字符串
// 1. 存储
localStorage.setItem('age', 18)
// 2. 获取
console.log(typeof localStorage.getItem('age'))
// 3. 删除
localStorage.removeItem('age')
script>
body>
html>
sessionStorage(了解)
特性:
- 用法跟
localStorage
基本相同
- 区别是:当页面浏览器被关闭时,存储在
sessionStorage
的数据会被清除
存储:sessionStorage.setItem(key,value)
获取:sessionStorage.getItem(key)
删除:sessionStorage.removeItem(key)
localStorage 存储复杂数据类型
问题: 本地只能存储字符串,无法存储复杂数据类型.
解决: 需要将复杂数据类型转换成 JSON字符串,在存储到本地
语法: JSON.stringify(复杂数据类型)
JSON
字符串:
- 首先是1个字符串
- 属性名使用
双引号
引起来,不能单引号
- 属性值如果是
字符串型也必须双引号
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))
// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods'))
script>
body>
问题: 因为本地存储里面取出来的是字符串,不是对象,无法直接使用
解决: 把取出来的字符串转换为对象
语法: JSON.parse(JSON字符串)
<body>
<script>
// 本地存储复杂数据类型
const goods = {
name: '小米',
price: 1999
}
// localStorage.setItem('goods', goods)
// console.log(localStorage.getItem('goods'))
// 1. 把对象转换为JSON字符串 JSON.stringify
localStorage.setItem('goods', JSON.stringify(goods))
// console.log(typeof localStorage.getItem('goods'))
// 2. 把JSON字符串转换为对象 JSON.parse
console.log(JSON.parse(localStorage.getItem('goods')))
script>
body>
4.JS常用的数组方法
map 使用场景:
map
可以遍历数组处理数据,并且返回新的数组
语法:
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (item, index) {
// console.log(item) // 数组元素
// console.log(index) // 索引号
return item + '颜色'
})
console.log(newArr)
script>
body>
数组join方法:
作用: join()
方法用于把数组中的所有元素转换一个字符串
语法:
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)
// 2. 数组join方法 把数组转换为字符串
// 小括号为空则逗号分割
console.log(newArr.join()) // red颜色,blue颜色,pink颜色
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join('')) //red颜色blue颜色pink颜色
console.log(newArr.join('|')) //red颜色|blue颜色|pink颜色
script>
body>
5.正则表达式
正则表达式(Regular Expression)是一种字符串匹配的模式(规则)
使用场景:
正则基本使用:
-
定义规则
const reg = /表达式/
- 其中
/ /
是正则表达式字面量
- 正则表达式也是
对象
-
使用正则
test()方法
用来查看正则表达式与指定的字符串是否匹配
- 如果正则表达式与指定的字符串匹配 ,返回
true
,否则false
<body>
<script>
// 正则表达式的基本使用
const str = 'web前端开发'
// 1. 定义规则
const reg = /web/
// 2. 使用正则 test()
console.log(reg.test(str)) // true 如果符合规则匹配上则返回true
console.log(reg.test('java开发')) // false 如果不符合规则匹配上则返回 false
script>
body>
元字符:
- 普通字符:
- 大多数的字符仅能够描述它们本身,这些字符称作
普通字符
,例如所有的字母和数字。
- 普通字符只能够
匹配字符串中与它们相同的字符
。
- 比如,规定用户只能输入英文26个英文字母,普通字符的话 /[abcdefghijklmnopqrstuvwxyz]/
- 元字符(特殊字符)
- 是一些具有特殊含义的字符,可以极大
提高了灵活性
和强大的匹配
功能。
- 比如,规定用户只能输入英文26个英文字母,换成元字符写法:
/[a-z]/
边界符:
正则表达式中的边界符(位置符)用来提示字符所处的位置
,主要有两个字符
如果^
和 $
在一起,表示必须是精确匹配
<body>
<script>
// 元字符之边界符
// 1. 匹配开头的位置 ^
const reg = /^web/
console.log(reg.test('web前端')) // true
console.log(reg.test('前端web')) // false
console.log(reg.test('前端web学习')) // false
console.log(reg.test('we')) // false
// 2. 匹配结束的位置 $
const reg1 = /web$/
console.log(reg1.test('web前端')) // false
console.log(reg1.test('前端web')) // true
console.log(reg1.test('前端web学习')) // false
console.log(reg1.test('we')) // false
// 3. 精确匹配 ^ $
const reg2 = /^web$/
console.log(reg2.test('web前端')) // false
console.log(reg2.test('前端web')) // false
console.log(reg2.test('前端web学习')) // false
console.log(reg2.test('we')) // false
console.log(reg2.test('web')) // true
console.log(reg2.test('webweb')) // flase
script>
body>
量词:
注意: 逗号左右两侧千万不要出现空格
<body>
<script>
// 元字符之量词
// 1. * 重复次数 >= 0 次
const reg1 = /^w*$/
console.log(reg1.test('')) // true
console.log(reg1.test('w')) // true
console.log(reg1.test('ww')) // true
console.log('-----------------------')
// 2. + 重复次数 >= 1 次
const reg2 = /^w+$/
console.log(reg2.test('')) // false
console.log(reg2.test('w')) // true
console.log(reg2.test('ww')) // true
console.log('-----------------------')
// 3. ? 重复次数 0 || 1
const reg3 = /^w?$/
console.log(reg3.test('')) // true
console.log(reg3.test('w')) // true
console.log(reg3.test('ww')) // false
console.log('-----------------------')
// 4. {n} 重复 n 次
const reg4 = /^w{3}$/
console.log(reg4.test('')) // false
console.log(reg4.test('w')) // flase
console.log(reg4.test('ww')) // false
console.log(reg4.test('www')) // true
console.log(reg4.test('wwww')) // false
console.log('-----------------------')
// 5. {n,} 重复次数 >= n
const reg5 = /^w{2,}$/
console.log(reg5.test('')) // false
console.log(reg5.test('w')) // false
console.log(reg5.test('ww')) // true
console.log(reg5.test('www')) // true
console.log('-----------------------')
// 6. {n,m} n =< 重复次数 <= m
const reg6 = /^w{2,4}$/
console.log(reg6.test('w')) // false
console.log(reg6.test('ww')) // true
console.log(reg6.test('www')) // true
console.log(reg6.test('wwww')) // true
console.log(reg6.test('wwwww')) // false
// 7. 注意事项: 逗号两侧千万不要加空格否则会匹配失败
script>
范围:
表示字符的范围,定义的规则限定在某个范围,比如只能是英文字母,或者数字等等
,用表示范围
<body>
<script>
// 元字符之范围 []
// 1. [abc] 匹配包含的单个字符, 多选1
const reg1 = /^[abc]$/
console.log(reg1.test('a')) // true
console.log(reg1.test('b')) // true
console.log(reg1.test('c')) // true
console.log(reg1.test('d')) // false
console.log(reg1.test('ab')) // false
// 2. [a-z] 连字符 单个
const reg2 = /^[a-z]$/
console.log(reg2.test('a')) // true
console.log(reg2.test('p')) // true
console.log(reg2.test('0')) // false
console.log(reg2.test('A')) // false
// 想要包含小写字母,大写字母 ,数字
const reg3 = /^[a-zA-Z0-9]$/
console.log(reg3.test('B')) // true
console.log(reg3.test('b')) // true
console.log(reg3.test(9)) // true
console.log(reg3.test(',')) // flase
// 用户名可以输入英文字母,数字,可以加下划线,要求 6~16位
const reg4 = /^[a-zA-Z0-9_]{6,16}$/
console.log(reg4.test('abcd1')) // false
console.log(reg4.test('abcd12')) // true
console.log(reg4.test('ABcd12')) // true
console.log(reg4.test('ABcd12_')) // true
// 3. [^a-z] 取反符
const reg5 = /^[^a-z]$/
console.log(reg5.test('a')) // false
console.log(reg5.test('A')) // true
console.log(reg5.test(8)) // true
script>
body>
字符类:
替换和修饰符
<body>
<script>
// 替换和修饰符
const str = '欢迎大家学习前端,相信大家一定能学好前端,都成为前端大神'
// 1. 替换 replace 需求:把前端替换为 web
// 1.1 replace 返回值是替换完毕的字符串
// const strEnd = str.replace(/前端/, 'web') 只能替换一个
script>
body>
修饰符约束正则执行的某些细节行为
,如是否区分大小写、是否支持多行匹配等
i
是单词 ignore 的缩写,正则匹配时字母``不区分大小写```
g
是单词 global 的缩写,匹配所有满足正则表达式的结果(全局)
<body>
<script>
// 替换和修饰符
const str = '欢迎大家学习前端,相信大家一定能学好前端,都成为前端大神'
// 1. 替换 replace 需求:把前端替换为 web
// 1.1 replace 返回值是替换完毕的字符串
// const strEnd = str.replace(/前端/, 'web') 只能替换一个
// 2. 修饰符 g 全部替换
const strEnd = str.replace(/前端/g, 'web')
console.log(strEnd)
script>
body>
6.作用域与作用域链
作用域
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)
规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域
和局部作用域
。
局部作用域:
局部作用域分为函数作用域
和块作用域
。
函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script>
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s) // 18
}
// 设用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 报错
script>
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的
局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量
实际被清空
了
块作用域:
在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
<script>
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age) // 报错
let flag = true;
if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!'
console.log(str); // 正常
}
// 超出了 age 的作用域
console.log(str); // 报错
for(let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了 t 的作用域
console.log(t); // 报错
script>
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】
,常量值为对象时其属性和方法允许重新赋值
。
<script>
// 必须要有值
const version = '1.0.0';
// 不能重新赋值
// version = '1.0.1';
// 常量值为对象类型
const user = {
name: '小明',
age: 18
}
// 不能重新赋值
user = {};
// 属性和方法允许被修改
user.name = '小小明';
user.gender = '男';
script>
总结:
let
声明的变量会产生块作用域,var
不会产生块作用域
const
声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用
let
或 const
注:开发中 let
和 const
经常不加区分的使用,如果担心某个值会不小心被修改时,则只能使用 const
声明成常量。
全局作用域:
标签和 .js
文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
<script>
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
script>
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
<script>
// 全局变量 name
const name = '小明'
// 函数作用域中访问全局
function sayHi() {
// 此处为局部
console.log('你好' + name)
}
// 全局变量 flag 和 x
const flag = true
let x = 10
// 块作用域中访问全局
if(flag) {
let y = 5
console.log(x + y) // x 是全局的
}
script>
总结:
- 为
window
对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制
,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// 局部作用域
function g() {
let d = 'yo'
}
}
script>
函数内部允许创建新的函数,f
函数内部创建的新函数 g
,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制
,在函数被执行时,会优先查找当前函数作用域中
查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域
直到全局作用域,如下代码所示:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 报错
// 局部作用域
function g() {
let d = 'yo'
// let b = 20;
console.log(b) // 2 或 20
}
// 调用 g 函数
g()
}
console.log(c) // 报错
console.log(d) // 报错
f();
script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
子作用域能够访问父作用域
,父级作用域无法访问子级作用域
7.JS垃圾回收机制
-
什么是垃圾回收机制?
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
-
内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
-
内存使用:即读写内存
,也就是使用变量、函数等
-
内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
说明
:
Ø 全局变量一般不会回收(关闭页面回收);
Ø 一般情况下局部变量的值, 不用了, 会被自动回收掉
四、JS高阶和ECMAScript(2015-2023)
1.闭包
闭包
是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量
。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
<body>
<script>
// 1. 闭包 : 内层函数 + 外层函数变量
// function outer() {
// const a = 1
// function f() {
// console.log(a)
// }
// f()
// }
// outer()
// 2. 闭包的应用: 实现数据的私有。统计函数的调用次数
// let count = 1
// function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
// 3. 闭包的写法 统计函数的调用次数
function outer() {
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`)
}
return fn
}
const re = outer()
// const re = function fn() {
// count++
// console.log(`函数被调用${count}次`)
// }
re()
re()
// const fn = function() { } 函数表达式
// 4. 闭包存在的问题: 可能会造成内存泄漏
script>
body>
总结:
1.怎么理解闭包?
闭包 = 内层函数 + 外层函数的变量
2.闭包的作用?
封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它
允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
内存泄漏
2.变量和函数提升
变量提升:
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问
<script>
// 访问变量 str
console.log(str + 'world!'); //undefinedworld!
// 声明变量 str
var str = 'hello ';
script>
总结:
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined
let
声明的变量不存在变量提升,推荐使用 let
- 变量提升出现在
相同作用域
当中
- 实际开发中推荐
先声明再访问变量
函数提升:
函数提升与变量提升比较类似,是指函数在声明之前即可被调用
。
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
script>
总结:
- 函数提升能够使函数的声明调用更灵活
函数表达式不存在提升的现象
- 函数提升出现在
相同作用域
当中
3.函数参数和es6箭头函数
函数参数的使用细节,能够提升函数应用的灵活度。
默认值:
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`大家好,我叫
${name},我今年${age}岁了。`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
script>
总结:
- 声明函数时为形参赋值即为参数的默认值
- 如果参数未自定义默认值时,参数的默认值为
undefined
- 调用函数时没有传入对应实参时,
参数的默认值被当做实参传入
动态参数:
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
<script>
// 求和函数,计算所有参数的和
function sum() {
// console.log(arguments) //[5,10]
let s = 0
for(let i = 0; i < arguments.length; i++) {
s += arguments[i]
}
console.log(s)
}
// 调用求和函数
sum(5, 10)// 两个参数
sum(1, 2, 4) // 两个参数
script>
总结:
arguments
是一个伪数组
arguments
的作用是动态获取函数的实参
剩余参数:
<script>
function config(baseURL, ...other) {
console.log(baseURL) // 得到 'http://baidu.com'
console.log(other) // other 得到 ['get', 'json']
}
// 调用函数
config('http://baidu.com', 'get', 'json');
script>
总结:
...
是语法符号,置于最末函数形参之前,用于获取多余的实参
- 借助
...
获取的剩余实参,是个真数组
箭头函数
箭头函数
是一种声明函数的简洁语法
,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
<body>
<script>
// const fn = function () {
// console.log(123)
// }
// 1. 箭头函数 基本语法
const fn = () => {
console.log(123)
}
fn()
// const fn = (x) => {
// console.log(x)
// }
// fn(1)
// 2. 只有一个形参的时候,可以省略小括号
// const fn = x => {
// console.log(x)
// }
// fn(1)
// // 3. 只有一行代码的时候,我们可以省略大括号
const fn = x => console.log(x)
fn(1)
// 4. 只有一行代码的时候,可以省略return
// const fn = x => x + x
// console.log(fn(1))
// 5. 箭头函数可以直接返回一个对象
// const fn = (uname) => ({ uname: uname })
// console.log(fn('刘德华'))
script>
body>
总结:
- 箭头函数属于表达式函数,因此
不存在函数提升
- 箭头函数
只有一个参数
时可以省略圆括号 ()
- 箭头函数
函数体只有一行代码
时可以省略花括号 {}
,并自动做为返回值被返回
箭头函数参数:
箭头函数中没有 arguments
,只能使用 ...
动态获取实参
<body>
<script>
// 1. 利用箭头函数来求和
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
const result = getSum(2, 3, 4)
console.log(result) // 9
script>
箭头函数 this:
箭头函数不会创建自己的this
,它只会从自己的作用域链的上一层沿用this
。
<script>
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this) // this 指向谁? window
// }
// }
// obj.sayHi()
const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this) // obj
}
count()
}
}
obj.sayHi()
script>
4.解构赋值
解构赋值是一种快速为变量赋值
的简洁语法,本质上仍然是为变量赋值,分为数组解构
、对象解构
两大类型。
数组解构:
数组解构是将数组的单元值快速批量赋值
给一系列变量的简洁语法,如下代码所示:
<script>
// 普通的数组
let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
script>
总结:
- 赋值运算符
=
左侧的 []
用于批量声明变量
,右侧数组的单元值将被赋值给左侧的变量
- 变量的顺序对应数组单元值的位置
依次进行赋值
操作
- 变量的数量
大于
单元值数量时,多余的变量将被赋值为 undefined
- 变量的数量
小于
单元值数量时,可以通过 ...
获取剩余单元值,但只能置于最末位
- 允许初始化变量的默认值,且只有单元值为
undefined
时默认值才会生效
注:支持多维解构赋值
对象解构:
对象解构是将对象属性和方法快速批量赋值
给一系列变量的简洁语法,如下代码所示:
<script>
// 普通对象
const user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
const {name, age} = user
console.log(name) // 小明
console.log(age) // 18
script>
总结:
- 赋值运算符
=
左侧的 {}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
- 对象属性的值将被赋值给与属性名相同的变量
- 对象中找不到与变量名一致的属性时变量值为
undefined
- 允许初始化变量的默认值,属性不存在或单元值为
undefined
时默认值才会生效
注:支持多维解构赋值
<body>
<script>
// 1. 这是后台传递过来的数据
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},
]
}
// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const { data } = msg
// console.log(data)
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
script>
5.forEach和filter
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选
。
<body>
<script>
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {
console.log(item) // 数组元素 red green pink
console.log(index) // 索引号
})
// console.log(result)
script>
body>
filter()
方法创建一个新的数组
,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<body>
<script>
const arr = [10, 20, 30]
// const newArr = arr.filter(function (item, index) {
// // console.log(item)
// // console.log(index)
// return item >= 20
// })
// 返回的符合条件的新数组
const newArr = arr.filter(item => item >= 20)
console.log(newArr)
script>
body>
6.构造函数
构造函数
是专门用于创建对象的函数,如果一个函数使用 new
关键字调用,那么这个函数就是构造函数
。
<script>
// 定义函数
function foo() {
console.log('通过 new 也能调用函数...');
}
// 调用函数
new foo;
script>
总结:
- 使用
new
关键字调用函数的行为被称为实例化
- 实例化构造函数时没有参数时可以省略
()
- 构造函数的
返回值即为新创建的对象
- 构造函数内部的
return
返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写
。
实例成员:
通过构造函数创建的对象称为实例对象
,实例对象中的属性和方法
称为实例成员
。
<script>
// 构造函数
function Person() {
// 构造函数内部的 this 就是实例对象
// 实例对象中动态添加属性
this.name = '小明'
// 实例对象动态添加方法
this.sayHi = function () {
console.log('大家好~')
}
}
// 实例化,p1 是实例对象
// p1 实际就是 构造函数内部的 this
const p1 = new Person()
console.log(p1)
console.log(p1.name) // 访问实例属性
p1.sayHi() // 调用实例方法
script>
总结:
- 构造函数内部
this
实际上就是实例对象,为其动态添加的属性和方法即为实例成员
- 为构造函数传入参数,
动态创建结构相同但值不同
的对象
注:构造函数创建的实例对象彼此独立互不影响
。
静态成员:
在 JavaScript 中底层函数本质上也是对象类型
,因此允许直接为函数动态添加属性或方法
,构造函数的属性和方法被称为静态成员
。
<script>
// 构造函数
function Person(name, age) {
// 省略实例成员
}
// 静态属性
Person.eyes = 2
Person.arms = 2
// 静态方法
Person.walk = function () {
console.log('^_^人都会走路...')
// this 指向 Person
console.log(this.eyes)
}
script>
总结:
- 静态成员指的是
添加到构造函数本身的属性和方法
- 一般
公共特征
的属性或方法静态成员设置为静态成员
- 静态成员方法中的
this
指向构造函数本身
内置构造函数
在 JavaScript 中最主要的数据类型有 6 种,分别是字符串
、数值
、布尔
、undefined
、null
和 对象
,常见的对象类型数据包括数组
和普通对象
。其中字符串、数值、布尔、undefined、null 也被称为简单类型或基础类型
,对象也被称为引用类型
。
在 JavaScript 内置了一些构造函数,绝大部的数据处理都是基于这些构造函数实现的,JavaScript 基础阶段学习的 Date
就是内置的构造函数。
<script>
// 实例化
let date = new Date();
// date 即为实例对象
console.log(date);
script>
Object
Object
是内置的构造函数,用于创建普通对象。
<script>
// 通过构造函数创建普通对象
const user = new Object({name: '小明', age: 15})
// 这种方式声明的变量称为【字面量】
let student = {name: '杜子腾', age: 21}
// 对象语法简写
let name = '小红';
let people = {
// 相当于 name: name
name,
// 相当于 walk: function () {}
walk () {
console.log('人都要走路...');
}
}
console.log(student.constructor);
console.log(user.constructor);
console.log(student instanceof Object);
script>
总结:
- 推荐使用字面量方式声明对象,而不是
Object
构造函数
Object.assign
静态方法创建新的对象
Object.keys
静态方法获取对象中所有属性
Object.values
表态方法获取对象中所有属性值
Array
Array
是内置的构造函数,用于创建数组。
<script>
// 构造函数创建数组
let arr = new Array(5, 7, 8);
// 字面量方式创建数组
let list = ['html', 'css', 'javascript']
script>
数组赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变。
总结:
-
推荐使用字面量方式声明数组,而不是 Array
构造函数
-
实例方法 forEach
用于遍历数组,替代 for
循环 (重点)
-
实例方法 filter
过滤数组单元值,生成新数组(重点)
-
实例方法 map
迭代原数组,生成新数组(重点)
-
实例方法 join
数组元素拼接为字符串,返回字符串(重点)
-
实例方法 find
查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined(重点)
-
实例方法every
检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false(重点)
-
实例方法some
检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false
-
实例方法 concat
合并两个数组,返回生成新数组
-
实例方法 sort
对原数组单元值排序
-
实例方法 splice
删除或替换原数组单元
-
实例方法 reverse
反转数组
-
实例方法 findIndex
查找元素的索引值
String
`String` 是内置的构造函数,用于创建字符串。
<script>
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串
let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor); // true
console.log(str instanceof String); // false
script>
总结:
- 实例属性
length
用来获取字符串的度长(重点)
- 实例方法
split('分隔符')
用来将字符串拆分成数组(重点)
- 实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])
用于字符串截取(重点)
- 实例方法
startsWith(检测字符串[, 检测位置索引号])
检测是否以某字符开头(重点)
- 实例方法
includes(搜索的字符串[, 检测位置索引号])
判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点)
- 实例方法
toUpperCase
用于将字母转换成大写
- 实例方法
toLowerCase
用于将就转换成小写
- 实例方法
indexOf
检测是否包含某字符
- 实例方法
endsWith
检测是否以某字符结尾
- 实例方法
replace
用于替换字符串,支持正则匹配
- 实例方法
match
用于查找字符串,支持正则匹配
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
Number
Number
是内置的构造函数,用于创建数值。
<script>
// 使用构造函数创建数值
let x = new Number('10')
let y = new Number(5)
// 字面量创建数值
let z = 20
script>
总结:
- 推荐使用字面量方式声明数值,而不是
Number
构造函数
- 实例方法
toFixed
用于设置保留小数位的长度
7.原型和原型链(重点)
原型对象:
构造函数通过原型
分配的函数是所有对象所共享的。
- JavaScript 规定,每一个
构造函数
都有一个 prototype
属性,指向另一个对象
,所以我们也称为原型对象
- 这个对象可以
挂载函数
,对象实例化不会多次创建原型上函数,节约内存
- 我们可以把那些不变的方法,
直接定义在 prototype 对象上
,这样所有对象的实例就可以共享
这些方法。
构造函数和原型对象中的this 都指向 实例化的对象
<script>
function Person() {
}
// 每个函数都有 prototype 属性
console.log(Person.prototype)
script>
了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象
具体的作用,如下代码所示:
<script>
function Person() {
// 此处未定义任何方法
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
// 实例化
let p1 = new Person();
p1.sayHi(); // 输出结果为 Hi~
script>
构造函数 Person
中未定义任何方法,这时实例对象调用了原型对象中的方法 sayHi
,接下来改动一下代码:
<script>
function Person() {
// 此处定义同名方法 sayHi
this.sayHi = function () {
console.log('嗨!');
}
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
let p1 = new Person();
p1.sayHi(); // 输出结果为 嗨!
script>
构造函数 Person
中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函中的方法 sayHi
。
通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时
,先在当前实例对象是查找
,然后再去原型对象查找
,并且原型对象被所有实例共享
。
<script>
function Person() {
// 此处定义同名方法 sayHi
this.sayHi = function () {
console.log('嗨!' + this.name)
}
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~' + this.name)
}
// 在构造函数的原型对象上添加属性
Person.prototype.name = '小明'
let p1 = new Person()
p1.sayHi(); // 输出结果为 嗨!小明
let p2 = new Person()
p2.sayHi()
script>
总结:结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象
中。
constructor 属性:
在哪里? 每个原型对象里面都有个constructor 属性
(constructor 构造函数
)
作用:该属性指向该原型对象的构造函数
, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值
.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor
就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中
,添加一个 constructor
指向原来的构造函数
。
function Star() {
}
Star.prototype = {
sing: function() {
console.log("唱歌");
},
dance: function() {
console.log("跳舞");
}
}
console.log(Star.prototype);
function Star() {
}
console.log(Star.prototype);
Star.prototype = {
// 重新指向创造这个原型对象的 构造函数
constructor:Star,
sing: function() {
console.log("唱歌");
},
dance: function() {
console.log("跳舞");
}
}
console.log(Star.prototype);
对象原型:
对象都会有一个属性__proto__
指向构造函数的 prototype 原型对象
,之所以我们对象可以使用构造函数 prototype
原型对象的属性和方法,就是因为对象有 proto 原型的存在。
注意:
__proto__
是JS非标准属性
- [[prototype]]
和__proto__
意义相同
- 用来表明
当前实例对象指向哪个原型对象prototype
__proto__
对象原型里面也有一个 constructor
属性,指向创建该实例对象的构造函数
原型继承
继承
是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承
的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
<body>
<script>
// 继续抽取 公共的部分放到原型上
// const Person1 = {
// eyes: 2,
// head: 1
// }
// const Person2 = {
// eyes: 2,
// head: 1
// }
// 构造函数 new 出来的对象 结构一样,但是对象不一样
function Person() {
this.eyes = 2
this.head = 1
}
// console.log(new Person)
// 女人 构造函数 继承 想要 继承 Person
function Woman() {
}
// Woman 通过原型来继承 Person
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person() // {eyes: 2, head: 1}
// 指回原来的构造函数
Woman.prototype.constructor = Woman
// 给女人添加一个方法 生孩子
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
console.log(red)
// console.log(Woman.prototype)
// 男人 构造函数 继承 想要 继承 Person
function Man() {
}
// 通过 原型继承 Person
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink)
script>
body>
原型链:
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系
称为原型链
<body>
<script>
// function Objetc() {}
console.log(Object.prototype)
console.log(Object.prototype.__proto__)
function Person() {
}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person)
console.log(ldh instanceof Object)
console.log(ldh instanceof Array)
console.log([1, 2, 3] instanceof Array)
console.log(Array instanceof Object)
script>
body>
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身
有没有该属性。
② 如果没有就查找它的原型
(也就是 __proto__指向的 prototype 原型对象
)
③ 如果还没有就查找原型对象的原型
(Object的原型对象
)
④ 依此类推一直找到 Object 为止
(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向
,或者说一条路线
⑥ 可以使用instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上
所有的对象
都有_proto_
对象原型指向原型对象,所有的原型对象prototype
都有constructor
指向创造原型对象的构造函数
8.深拷贝和浅拷贝
浅拷贝:
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
- 拷贝对象:
Object.assgin()
/ 展开运算符 {...obj}
拷贝对象
- 拷贝数组:
Array.prototype.concat()
或者 [...arr]
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址
(简单理解: 如果是单层对象,没问题,如果有多层就有问题)
深拷贝:
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象
,不是地址
常见方法:
- 通过
递归
实现深拷贝
lodash
/cloneDeep
- 通过
JSON.stringify()
实现
递归实现深拷贝:
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 简单理解:函数
内部自己调用自己
, 这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“
栈溢出
”错误(stack overflow),所以必须要加退出条件 return
<body>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = {}
// 拷贝函数
function deepCopy(newObj, oldObj) {
debugger
for (let k in oldObj) {
// 处理数组的问题 一定先写数组 在写 对象 不能颠倒
if (oldObj[k] instanceof Array) {
newObj[k] = []
// newObj[k] 接收 [] hobby
// oldObj[k] ['乒乓球', '足球']
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
// k 属性名 uname age oldObj[k] 属性值 18
// newObj[k] === o.uname 给新对象添加属性
newObj[k] = oldObj[k]
}
}
}
deepCopy(o, obj) // 函数调用 两个参数 o 新对象 obj 旧对象
console.log(o)
o.age = 20
o.hobby[0] = '篮球'
o.family.baby = '老pink'
console.log(obj)
console.log([1, 23] instanceof Object)
// 复习
// const obj = {
// uname: 'pink',
// age: 18,
// hobby: ['乒乓球', '足球']
// }
// function deepCopy({ }, oldObj) {
// // k 属性名 oldObj[k] 属性值
// for (let k in oldObj) {
// // 处理数组的问题 k 变量
// newObj[k] = oldObj[k]
// // o.uname = 'pink'
// // newObj.k = 'pink'
// }
// }
script>
body>
js库lodash
里面cloneDeep
内部实现了深拷贝
<body>
<script src="./lodash.min.js">script>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = _.cloneDeep(obj)
console.log(o)
o.family.baby = '老pink'
console.log(obj)
script>
body>
JSON序列化:
<body>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
// 把对象转换为 JSON 字符串
// console.log(JSON.stringify(obj))
const o = JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby = '123'
console.log(obj)
script>
body>
9.this和改变this指向的三种方法
普通函数:
普通函数的调用方式决定了 this
的值,即【谁调用 this
的值指向谁】,如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this)
}
// 函数表达式
const sayHello = function () {
console.log(this)
}
// 函数的调用方式决定了 this 的值
sayHi() // window
window.sayHi()
// 普通对象
const user = {
name: '小明',
walk: function () {
console.log(this)
}
}
// 动态为 user 添加方法
user.sayHi = sayHi
uesr.sayHello = sayHello
// 函数调用方式,决定了 this 的值
user.sayHi()
user.sayHello()
script>
注: 普通函数没有明确调用者时 this
值为 window
,严格模式下没有调用者时 this
的值为 undefined
。
箭头函数:
箭头函数中的 this
与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this
!箭头函数中访问的 this
不过是箭头函数所在作用域的 this
变量。
<script>
console.log(this) // 此处为 window
// 箭头函数
const sayHi = function() {
console.log(this) // 该箭头函数中的 this 为函数声明环境中 this 一致
}
// 普通对象
const user = {
name: '小明',
// 该箭头函数中的 this 为函数声明环境中 this 一致
walk: () => {
console.log(this)
},
sleep: function () {
let str = 'hello'
console.log(this)
let fn = () => {
console.log(str)
console.log(this) // 该箭头函数中的 this 与 sleep 中的 this 一致
}
// 调用箭头函数
fn();
}
}
// 动态添加方法
user.sayHi = sayHi
// 函数调用
user.sayHi()
user.sleep()
user.walk()
script>
在开发中【使用箭头函数前需要考虑函数中 this
的值】,事件回调函数使用箭头函数时,this
为全局的 window
,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
<script>
// DOM 节点
const btn = document.querySelector('.btn')
// 箭头函数 此时 this 指向了 window
btn.addEventListener('click', () => {
console.log(this)
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click', function () {
console.log(this)
})
script>
同样由于箭头函数 this
的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
<script>
function Person() {
}
// 原型对像上添加了箭头函数
Person.prototype.walk = () => {
console.log('人都要走路...')
console.log(this); // window
}
const p1 = new Person()
p1.walk()
script>
改变this指向:
以上归纳了普通函数和箭头函数中关于 this
默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this
的指向,有 3 个方法可以动态指定普通函数中 this
的指向:
call:
使用 call
方法调用函数,同时指定函数中 this
的值,使用方法如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16
}
// 调用函数并指定 this 的值
sayHi.call(user); // this 值为 user
sayHi.call(student); // this 值为 student
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.call(null, 5, 10);
console.log(result);
script>
总结:
call
方法能够在调用函数的同时指定 this
的值
- 使用
call
方法调用函数时,第1个参数为 this
指定的值
call
方法的其余参数会依次自动传入函数做为函数的参数
apply:
使用 call
方法调用函数,同时指定函数中 this
的值,使用方法如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this)
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16
}
// 调用函数并指定 this 的值
sayHi.apply(user) // this 值为 user
sayHi.apply(student) // this 值为 student
// 求和函数
function counter(x, y) {
return x + y
}
// 调用 counter 函数,并传入参数
let result = counter.apply(null, [5, 10])
console.log(result)
script>
总结:
apply
方法能够在调用函数的同时指定 this
的值
- 使用
apply
方法调用函数时,第1个参数为 this
指定的值
apply
方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind:
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数,使用方法如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this)
}
let user = {
name: '小明',
age: 18
}
// 调用 bind 指定 this 的值
let sayHello = sayHi.bind(user);
// 调用使用 bind 创建的新函数
sayHello()
script>
注:bind
方法创建新的函数,与原函数的唯一的变化是改变了 this
的值。
10.防抖和节流
防抖
(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次
,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
节流
(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数