1.let和var的作用域区别
var的声明范围是函数作用域,let的声明范围是块级作用域
js
if (true) {
var name = 'Matt';
console.log(name); // Matt
}
console.log(name); // Matt
if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age没有定义
在js中,基本可以理解为一个{}就代表一个块作用域,if(){}内定义的属于块作用域,但不属于函数作用域。
看另一个场景:
js
for(var i = 0;i<5;i++){
setTimeout(()=>{
console.log(i,'i') // 5 5 5 5 5
})
}
for(let j = 0;j<5;j++){
setTimeout(()=>{
console.log(j,'j') // 0 1 2 3 4
})
}
上面的流程: 因为var定义的是一个全局变量,for循环是同步任务,所以for循环了5次,这个全局变量i每次都加1,最终for循环执行完5次之后,i就成了5。然后setTimeout在作为异步任务,在最后一次for循环完成之后开始执行,每次都会拿到全局变量i,执行5次,所以是5个5。
下面的流程: let定义的是块作用域,每个for循环都会定义一个块,所以相当于产生了5个块作用域,每个块作用域都保留一个当前独立的j变量。setTimeout在同步代码执行完之后执行异步代码,每次访问的都是当前的块作用域内的j变量,即每次访问的都是一个个独立的j,所以是输出结果是0 1 2 3 4
同异步和var & let对比:
js
for(var i = 0;i<5;i++){
console.log(i,'i的同步打印')
setTimeout(()=>{
console.log(i,'i的异步打印')
})
}
for(let j = 0;j<5;j++){
console.log(j,'j的同步打印')
setTimeout(()=>{
console.log(j,'j的异步打印')
})
}
打印结果:

2.undefined和null代表什么?
undefined代表未初始化变量,null代表空对象指针。
js
let a
console.log(a,'a是未初始化变量') // undefined a是未初始化变量
let obj = null
console.log(typeof obj,'null是空对象指针') // object null是空对象指针
console.log(null == undefined, '==结果') // true '==结果'
console.log(null === undefined, '===结果') // false '===结果'
3.js的小数计算不精确
js小数计算不精确,所以不要用js进行小数计算
js
let num1 = 0.1
let num2 = 0.2
if(num1 + num2 == 0.3){
console.log('结果成立') //这里没打印
}
console.log(num1 + num2,'num1+num2的结果') // 0.30000000000000004 'num1+num2的结果'
4.number的最大值和最小值范围
js中number能表示的最大值是Number.MAX_VALUE,最小值是Number.MIN_VALUE,检测方法isFinite(xxx)
js
console.log(Number.MAX_VALUE,'js的number的最大值') // 1.7976931348623157e+308 'js的number的最大值'
console.log(Number.MIN_VALUE,'js的number的最小值') // 5e-324 'js的number的最小值
console.log(1.7976931348623157e+308 * 2,'最大值的两倍') // Infinity '最大值的两倍'
console.log(isFinite((1.7976931348623157e+308) * 2), '是否在js的number范围内') // false '是否在js的number范围内'
5.模板字面量标签函数
当模板字符串前面有一个函数名时,该函数会接收两个参数:一个字符串数组和一个由所有表达式结果组成的数组。这个函数可以对模板字符串和表达式结果进行自定义处理。
js
let a = 6;
let b = 9;
function simpleTag1(strings, aValExpression, bValExpression, sumExpression) {
console.log(strings);
console.log(aValExpression);
console.log(bValExpression);
console.log(sumExpression);
return 'foobar1';
}
let untaggedResult = `${a} + ${b} = ${a + b}`;
let taggedResult1 = simpleTag1`${a}+${b}=${a + b}`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(untaggedResult); // "6 + 9 = 15"
console.log(taggedResult1); // "foobar1"
function simpleTag2(strings, ...expressions) {
console.log(strings);
console.log(expressions,)
return 'foobar2';
}
let taggedResult2 = simpleTag2`${a}+${b}=${a + b}`;
// ["", " + ", " = ", ""]
// [6, 9, 15]
console.log(taggedResult2); // "foobar2"
这里解释一下执行过程,例如simpleTag2函数,在24行的函数调用中,函数后面并没有像传统的simpleTag2(xxx),而是跟了一个模板字符串,这个函数在调用时就变成了模板字面量标签函数。
这个模板字符串的结果是字符串 6 + 9 = 15
,其中+和 = 以及空格这两个字符是当前模板字符串计算之前就存在的,而6 9 15是模板字符串计算之后的结果。计算之前的就字符集合是第一个参数,计算后的字符集合则作为第二个参数。
作用就在于,当你有一个模板字符串,而这个模板字符串中有一些动态的值,但是你想知道哪些字符是写死的,哪些是动态的值,就很容易分辨出来,如下例子:
js
let person1 = '王惊涛'
let age1 = 29
let person2 = '孙悟空'
let age2 = 800
let nickName = '齐天大圣'
function filterString(fix,...active){
console.log(fix,'固定的字符')
console.log(active,'动态的字符')
}
filterString`我是${person1},我今年${age1}岁`
// ['我是', ',我今年', '岁', raw: Array(3)] '固定的字符'
// ['王惊涛', 29] '动态的字符'
filterString`我是${person2},我今年${age2}岁,我的外号是-- ${nickName}`
// ['我是', ',我今年', '岁,我的外号是-- ', '', raw: Array(4)] '固定的字符'
// ['孙悟空', 800, '齐天大圣'] '动态的字符'
所有代码执行结果总览:

6.对象是按引用传递的
js中所有函数的参数都是按值传递的,当一个对象作为函数的参数,在函数内修改对象的属性,会导致外部对象属性的变化。但如果在函数内部重新给参数赋值,即让参数指向一个新的对象,这个时候对象属性的修改就不会影响到外部的对象属性,因为对象是按引用传递的。
js中函数的参数是局部变量。
js
function setName(obj) {
obj.name = "王惊涛";
obj=new Object();
obj.name = "孙悟空";
}
let person = new Object();
setName(person);
console.log(person.name); // "王惊涛"
7.执行上下文
函数在执行时,会创建一个执行上下文(作用域),这个上下文决定了变量的生命周期。
1.执行上下文分全局上下文、函数上下文和块级上下文。
2.代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
3.函数或块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
4.全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
5.变量的执行上下文用于确定什么时候释放内存。
8.Global对象和window
Global对象是js中最特别的对象,存在于全局作用域,隐式的存在,无法主动访问,所有的实例方法和全局作用域中定义的变量都会成为Global对象的属性。例如用var在全局定义一个变量或者function在全局定义一个函数,都会存在Global对象中。
window对象是Global的代理。当然,window不等于Global,只是可以借助window去访问Global对象上的很多属性。
Global对象上的属性:

9.对象解构赋值的一些特殊操作
解构赋值在实际开发中用的频率大多数取决于个人爱好,很多人几乎从来不用。
有时候,用一些特殊的语法,看起来很优雅
关键知识点:
1.设置别名,更加的见名知意
2.提取原始值(字符串,数字,布尔)的属性
3.嵌套取值
4.函数内解构参数
示例:
js
//1.设置别名 格式为: let {内部属性:别名} = 对象
let wjt = {
name:'wjt',
age: 28
}
let {name:WjtName,age:WjtAge} = wjt
console.log(WjtName,WjtAge,'WjtName和WjtAge')
//2.当一个变量本身不是object类型,但是对其使用了解构赋值,就会在内部使用ToObject()把源数据结构转化为对象, null和undefiend不能被解构
let name = 'wjt'
let age = 28
let {length} = name
console.log(length,'length: name的长度')
let {constructor} = age
console.log(constructor === Number, '指向包装对象的原型')
//3. 嵌套结构 格式为: let {属性:{子属性}} = 对象 ,可以多级嵌套
let Jarvan = {
name:'嘉文四世',
nickName:'德玛西亚皇子',
skill:{
q:'巨龙撞击',
r:'山崩地裂'
}
}
let {name:JarvanName,skill:{q:JarvanQSkill}} = Jarvan
console.log(`我是${JarvanName},我的q技能是${JarvanQSkill}`)
//4.函数参数也是可以的
let fn = ({name:PersonName,age})=>{
console.log(`我是${PersonName},我今年${age}岁`)
}
fn(wjt)
效果:

10.继承方法
1.原型链继承:
js
console.log('-----------------------原型式继承----------------------------')
function Fun1() {
this.type = '人类'
this.hasFun = ['听力', '视力', '语言能力', '思考能力', '行动能力']
}
function Man(name) {
this.name = name
this.sex = '男性'
}
//完成了原型链继承
Man.prototype = new Fun1
let wjt = new Man('王惊涛')
console.log(wjt, 'wjt')
解析:将构造函数Fun1创建的实例对象,作为构造函数Man的原型对象。这样,构造函数Man创建的实例对象就会继承构造函数1的属性。

2.盗用构造函数(经典继承):
js
console.log('-----------------------盗用构造函数继承----------------------------')
function Fn1() {
this.list = ['王惊涛', '孙悟空']
}
function Fn2() {
Fn1.call(this)
}
let list1 = new Fn2()
list1.list.push('德莱厄斯')
console.log(list1.list, 'list1的list属性')
let list2 = new Fn2()
console.log(list2.list, 'list2的list属性')
解析:在构造函数Fn2中调用构造函数Fn1,并且使用apply或者call方法以新创建的对象作为上下文执行构造函数。就好比Fn2偷了Fn1的this,拿到了并继承了fn1中this的属性。

3.组合继承:
js
//moba游戏
console.log('-----------------------组合继承----------------------------')
function MobaGame(name) {
this.name = name
this.props = ['峡谷', '乱斗', '克隆', '下棋']
}
MobaGame.prototype.download = function () {
return `下载${this.game},注册账号`
}
//腾讯游戏
function TencentGame(name, type) {
MobaGame.call(this, name)
this.type = type
}
TencentGame.prototype = new MobaGame()
TencentGame.prototype.play = function () {
console.log(`这是${this.name},需要在${this.type}上打开`)
}
let lol = new TencentGame('英雄联盟', 'pc端')
lol.props.push('竞技场')
console.log(lol, 'lol的值')
lol.download()
lol.play()
let hok = new TencentGame('王者荣耀', '移动端')
hok.props.push('火焰山')
console.log(hok, 'hok的值')
hok.download()
hok.play()
解析:通过给原型链继承的方式(即构造函数.protptype = xxx)给两个构造函数的原型上添加一些属性,再通过调用函数,让构造函数TencentGame执行时调用call方法实现盗用继承,得到了MobaGame原型上的属性。

4.原型式继承:
js
console.log('-----------------------原型式继承----------------------------')
function family(person) {
function Things() { }
Things.prototype = person;
return new Things();
}
let member = {
name: "家庭成员",
furniture: ['冰箱','床','空调']
};
let Wjt= family(member);
Wjt.name = "王惊涛";
Wjt.furniture.push("电脑");
console.log(Wjt,'Wjt打印')
let Ms = family(member);
Ms.name = "小马";
Ms.furniture.push("无线网络");
console.log(Ms,'Ms打印');
解析:在family函数中创建一个构造函数Things,给Things的原型上上添加family函数传入的属性,然后将这个构造函数创建的实例返回出去。根据同一原型创建的多个对象,实现了信息的共享,family可以替换成Object.create方法。

5.寄生式继承:
js
console.log('-----------------------寄生式继承----------------------------')
//模拟器
function simulator(game){
let processor = Object.create(game)
processor.start = function(){
console.log(`我是${this.name},我可以在pc端运行了`)
}
return processor
}
let hok_phone = {
name:'王者荣耀',
desc:'我是个手游,只能在手机上玩'
}
let hok_computer = simulator(hok_phone)
hok_computer.start()
解析:在函数内部使用Object.create方法创建一个对象,这个对象的原型就是create中的参数,在函数内将这个对象的属性增强,并作为函数的返回值。调用宿主函数就可以得到继承了这些属性的对象。

6.寄生式组合继承
js
console.log('-----------------------寄生式组合继承----------------------------')
//兼容器
function compatibility(android,ios){
let prototype = Object.create(android.prototype) //创建对象
prototype.constructor = ios //增强对象
android.prototype = prototype //给原型赋值增强后的对象
}
function Android(name){
this.name = name
this.fun = function(){
console.log('可以运行安卓的app')
}
}
Android.prototype.addApp = function(){
console.log('玩一些破解版的游戏')
}
function Ios(name,appStore){
Android.call(this,name)
this.appStore = appStore
}
compatibility(Android,Ios)
Ios.prototype.app = function(){
console.log(this.appStore)
}
let superIphone = new Ios('苹果手机',['苹果钱包','苹果相机'])
console.log(superIphone,'增强后的苹果手机')
superIphone.fun()
superIphone.app()
解析: 将寄生式继承和组合继承一起用上,compatibility中Object.create方法构建对象作为传入对象的原型,Ios函数利用call方法完成组合继承。让最后的superIphone对象也获取到了Android构造函数中的fun属性。

7.类继承
js
console.log('-----------------------类继承extends----------------------------')
class Car {
constructor(){
this.cnme = '汽车'
}
done = '行驶'
}
class ElectricCar extends Car{
constructor(name){
super()
this.name = name
}
}
ElectricCar.prototype.source = '电动能源'
let biyadi = new ElectricCar('比亚迪电动车')
console.log(biyadi,'比亚迪汽车')
解析:类继承使用关键字extends,简单方便,不过super关键字必须写在this前面

11.Reflect对象
Reflect对象主要是用来拦截操作方法的,api和Proxy的方法相同。
js
//Reflect 对象是一个全局的普通对象。Reflect 的原型是 Object
let obj = {}
console.log(Reflect.__proto__ === Object.prototype,'Reflect.__proto__ === Object.prototype的执行结果')
console.log(obj._proto_ === Reflect._proto_,'obj._proto_ === Reflect._proto_的执行结果')
// Reflect可以拿到语言内部的方法,里面的属性和Proxy是一样的
let obj1 = {
name:'wjt'
}
console.log(Reflect.get(obj1,'name'),'Reflect.get的方法使用')
12.尾调用函数
外部函数的返回值是一个内部函数的返回值
js
//例如: fn2的返回值就是fn1的返回值
function fn1(){
function fn2(){
return 'fn2_value'
}
return fn2()
}
不符合场景:
js
"use strict";
// 无优化:尾调用没有返回
function outerFunction() {
innerFunction();
}
// 无优化:尾调用没有直接返回
function outerFunction() {
letinnerFunctionResult=innerFunction();
returninnerFunctionResult;
}
// 无优化:尾调用返回后必须转型为字符串
function outerFunction() {
return innerFunction().toString();
}
// 无优化:尾调用是一个闭包
function outerFunction() {
letfoo='bar';
function innerFunction() { returnfoo;}
return innerFunction();
}
符合场景:
js
"use strict";
// 有优化:栈帧销毁前执行参数计算
function outerFunction(a, b) {
return innerFunction(a + b);
}
// 有优化:初始返回值不涉及栈帧
function outerFunction(a, b) {
if (a < b) {
return a;
}
return innerFunction(a + b);
}
// 有优化:两个内部函数都在尾部
function outerFunction(condition) {
return condition ? innerFunctionA() : innerFunctionB();
}
场景:斐波纳契数列函数
js
function fib1(n) {
if (n < 2) {
return n;
}
return fib1(n -1) + fib1(n -2);
}
console.log(fib1(0)); // 0
console.log(fib1(1)); // 1
console.log(fib1(2)); // 1
console.log(fib1(3)); // 2
console.log(fib1(4)); // 3
console.log(fib1(5)); // 5
console.log(fib1(6)); // 8
显然这个函数不符合尾调用优化的条件,因为返回语句中有一个相加的操作。结果,fib1(n)的栈帧数的内存复杂度是O(2n)。例如: fib1(100);
优化后的代码:
js
"use strict";
// 基础框架
function fib2(n) {
return fibImpl(0, 1, n);
}
// 执行递归
function fibImpl(a, b, n) {
if (n === 0) {
return a;
}
return fibImpl(b, a + b, n -1);
}
fib2(100)
13.闭包中的this问题
js
window.val = 'val1'
let obj1 = {
val:'obj1',
fn(){
return function(){
return this.val
}
}
}
console.log('第一种情况',obj1.fn()())
let obj2 = {
val:'obj2',
fn(){
let that = this
return function(){
return that.val
}
}
}
console.log('第二种情况',obj2.fn()())

解析:
第一种情况,当obj1.fn函数被调用,将一个匿名函数作为返回值返回到外部。这个匿名函数的执行上下文就是window,所以返回的就是window中val。
第二种情况:当obj2.fn函数被调用,fn函数中的执行上下文是obj2,所以给that的值就是obj2,最后匿名函数的返回值也就是obj2.val
14.窗口的打开与关闭
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>浏览器窗口</title>
</head>
<body>
<div>
<button onclick="open1()">打开新窗口1</button>
<button onclick="close1()">关闭新窗口1</button>
<button onclick="checkInfo()">查看窗口信息</button>
</div>
<script>
let window1 = null
function open1(){
window1 = window.open("http://www.baidu.com/",
"wroxWindow",
"height=400, width=400, top=10, left=10, resizable=yes");
}
function close1(){
if(window1){
window1.close()
window1 = null
}
}
function checkInfo(){
console.log(window.self,'window.self')
console.log(window.parent,'window.parent')
console.log(window.top,'window.top')
console.log(window.parent.parent,'window.parent.parent')
}
</script>
</body>
</html>

15.location对象
location对象提供了当前窗口中加载的文档信息,以及通常的导航功能。window.location和document.location是一样的。
html
// location中有很多属性,打印就可以看到
console.log(location,'location对象')
// search参数,例如: www.baidu.com?name=wjt&id=1
// ?后面跟的就是search参数
// 标准查询方法: URLSearchParams,通过这个方法可以检查和修改查询字符串,该方法是一个构造函数。有get(),set(),delete()方法
let qs = "?name=wjt&id=1"
let searchParams = new URLSearchParams(qs)
console.log(searchParams.toString(),'searchParams.toString()的值')
console.log(searchParams.has('name'),'查看是否存在name键')
console.log(searchParams.has('id'),'查看是否存在id键')
console.log(searchParams.has('value'),'查看是否存在value键')
searchParams.set('value','aaa')
console.log(searchParams.has('value'),'调用set后查看是否存在value键')
searchParams.delete('value')
console.log(searchParams.has('value'),'调用delete后查看是否存在value键')
// location.reload() 重新加载,如果页面有缓存,就会加载缓存
// location.reload(true) 强制性的重新加载,从服务器加载

16.dom元素新增子节点
domNode.appendChild(newNode): 在末尾添加一个元素
domNode.insertBefore(newNode,referNode): 以reerNode为参照物,在后面添加一个元素
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dom节点新增子元素</title>
</head>
<style>
li{
height: 30px;
line-height: 30px;
}
</style>
<body>
<div>
<input type="text" value="" id="input" placeholder="在这里输入添加的位置">
<button onclick="addInLast()">在末尾添加一个新元素</button>
<button onclick="addInAny()">在任意一个地方添加一个新元素</button>
</div>
<ul id="ulBox">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const ulBox = document.querySelector('#ulBox')
const inputBox = document.querySelector('#input')
const addInLast = () =>{
let newNode = document.createElement('li')
newNode.innerText = ulBox.getElementsByTagName('li').length + 1
ulBox.appendChild(newNode)
}
const addInAny = () =>{
if(parseInt(inputBox.value) === NaN){
alert('请输入数字')
return
}
if(parseInt(inputBox.value)>ulBox.getElementsByTagName('li').length){
alert('输入数字太大,重新输入')
return
}
let newNode = document.createElement('li')
let referNode = ulBox.getElementsByTagName('li')[parseInt(inputBox.value)]
ulBox.insertBefore(newNode,referNode)
newNode.innerText = '临时添加的'
}
</script>
</body>
</html>

17.元素自定义属性
可以通过根据setAttribute新增一个属性,根据getAttribute查询属性,根据removeAttribute删除属性
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>元素自定义属性</title>
</head>
<body>
<div class="box" id="divNode" prop1="标签上声明">一个div元素</div>
<script>
const box = document.querySelector('#divNode')
//查
console.log(box.getAttribute('class'),'class属性')
console.log(box.getAttribute('id'),'id属性')
console.log(box.getAttribute('prop1'),'prop1属性')
console.log(box.getAttribute('not'),'不存在的属性')
//增
box.setAttribute('prop2','使用set声明')
console.log(box.getAttribute('prop2'),'prop2属性')
//改
box.setAttribute('prop2','修改了一下')
console.log(box.getAttribute('prop2'),'prop2属性-修改后')
//删
box.removeAttribute('prop2')
console.log(box.getAttribute('prop2'),'prop2属性-删除后')
</script>
</body>
</html>

18.MutationObserver方法
MutationObserver可以用来监测DOM的变化,通过new一个实例,根据元素的标识去判断当前变化的元素是哪个,然后做对应的处理
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MutationObserver方法</title>
<style>
.doneBox{
margin-bottom: 100px;
}
.box1 {
width: 30px;
height: 30px;
background: red;
margin: 100px;
}
.box2 {
width: 30px;
height: 30px;
background: blue;
margin: 100px;
}
</style>
</head>
<body>
<div class="doneBox">
<button onclick="done1()">让红盒子变大</button>
<button onclick="done2()">让蓝盒子变大</button>
</div>
<div class="box1"></div>
<div class="box2"></div>
<script>
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')
const done1 = () => {
let width = parseInt(box1.offsetWidth)
let height = parseInt(box1.offsetHeight)
box1.style.width = `${width+10}px`
box1.style.height = `${height+10}px`
}
const done2 = () => {
let width = parseInt(box2.offsetWidth)
let height = parseInt(box2.offsetHeight)
box2.style.width = `${width+10}px`
box2.style.height = `${height+10}px`
}
const observer = new MutationObserver((changeList)=>{
console.log('有元素发生了变化',changeList)
switch(changeList[0].target){
case box1:
console.log('红盒子发生了变化')
break
case box2:
console.log('蓝盒子发生了变化')
break
}
})
//attributes:true 观察属性变化
//attributeOldValue:true 会记录变化前的状态
//childList:true 是否观察子节点变化
//subtree:true 对子树节点的深度监测
observer.observe(box1,{attributes:true,attributeOldValue:true})
observer.observe(box2,{attributes:true})
</script>
</body>
</html>

19.dataset的使用
dataset同样可以自定义的在元素上设置一些属性
在元素上使用data-xxx="yyy"设置一个属性,可以在dom.dataset.xxx访问到yyy
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dataset使用</title>
</head>
<body>
<p data-aaa="111" id="p1">段落1</p>
<p data-bbb="222" id="p2">段落2</p>
<script>
const p1 = document.querySelector('#p1')
const p2 = document.querySelector('#p2')
console.log(p1.dataset.aaa,'p1的data-aaa属性')
console.log(p2.dataset.bbb,'p2的data-bbb属性')
p1.dataset.aaa = '哈哈哈'
console.log(p1.dataset.aaa,'修改后p1的data-aaa属性')
</script>
</body>
</html>

20.窗口滚动复位
domNode.scrollIntoView(),可以让窗口直接定位到domNode所在的位置,处于左上角
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>窗口滚动复位</title>
<style>
.box {
height: 5000px;
}
.innerBox {
width: 200px;
height: 200px;
background: red;
margin-top: 3000px;
}
</style>
</head>
<body>
<div class="box">
<button onclick="findBox()">滚动到红色盒子</button>
<div class="innerBox"></div>
</div>
<script>
const findBox = ()=>{
let box = document.querySelector('.innerBox')
box.scrollIntoView()
}
</script>
</body>
</html>

21.事件相关
阻止事件冒泡: event.stopPropagation
阻止默认事件: event.preventDefault
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件相关的一些东西</title>
<style>
body{
height: 100vh;
}
#box{
height: 100px;
background: blueviolet;
}
</style>
</head>
<body id="body">
<div id="box">
<button id="btn">按钮</button>
<a id="a" href="www.baidu.com" onclick="jump()">跳转</a>
</div>
<script>
let body = document.querySelector('#body')
let box = document.querySelector('#box')
let btn1 = document.querySelector('#btn')
let a = document.querySelector('#a')
body.onclick = (event)=>{
console.log('body被点击了')
}
box.onclick = (event)=>{
console.log('box被点击了')
//阻止事件冒泡,也就是说当box被点击时,事件就不会向父节点传递,body.onclick的函数就不会被触发
event.stopPropagation()
}
btn.onclick = (event)=>{
console.log('按钮被点击了')
//没有阻止,点击事件会继续往上传递,box.onclick事件会被触发
}
a.onclick = (event)=>{
//让a标签的默认事件,比如href跳转失效
event.preventDefault()
console.log('a标签被点击了')
}
</script>
</body>
</html>

22.右键事件
可以通过给contextmenu绑定方法,自定义右键菜单,阻止原来的默认事件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>右键事件</title>
<style>
#myDiv{
height:200px;
background: pink;
}
li{
width: 100px;
height: 30px;
line-height: 30px;
}
</style>
</head>
<body>
<div id="myDiv">
自定义右键生效区域
</div>
<ul id="myMenu" style="position:absolute; visibility:hidden; background-color:
silver">
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
</ul>
<script>
window.addEventListener("load", (event) => {
let div = document.getElementById("myDiv");
div.addEventListener("contextmenu", (event) => {
event.preventDefault();
let menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
});
document.addEventListener("click", (event) => {
document.getElementById("myMenu").style.visibility = "hidden";
});
});
</script>
</body>
</html>

23.postMessage消息传递
通过postMessage可以给其他网页传递消息,被传递消息的网页可以通过监听message事件和来源,对应的做出回应
传递消息的网页
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>postMessage消息传递</title>
</head>
<body>
<div>
<h1>父网页</h1>
<button onclick="postMsg()">给子网页传递一条消息</button>
<p id="p"></p>
</div>
<div>
<iframe id="iframePage" src="./23.被传递消息的网页.html" frameborder="0" width="1000px" height="500px"></iframe>
</div>
<script>
const iframePage = document.querySelector('#iframePage').contentWindow
const p = document.querySelector('#p')
function postMsg (){
iframePage.postMessage({name:'wjt',flag:'专属标志'},'http://127.0.0.1:5500')
}
window.addEventListener("message", (event) => {
// 确保来自预期发送者
if (event.origin == "http://127.0.0.1:5500") {
if(event.data && event.data.flag === '子网页的专属标志'){
console.log('收到子网页的回信')
p.innerText = '收到子网页的回信'
}
}
});
</script>
</body>
</html>
被传递消息的网页
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>被传递消息的网页</title>
</head>
<body>
<div>我是子网页</div>
<p id="p"></p>
<script>
console.log('进入子网页')
let p = document.querySelector('#p')
window.addEventListener("message", (event) => {
// 确保来自预期发送者
if (event.origin == "http://127.0.0.1:5500") {
if (event.data && event.data.flag === '专属标志') {
console.log('接收到了父网页的消息')
p.innerText = '已收到父组件传递的消息'
}
//向来源窗口发送一条消息
event.source.postMessage({ name: 'qingyang', flag: '子网页的专属标志' }, "http://127.0.0.1:5500");
}
});
</script>
</body>
</html>
在本地开启服务,默认5500

24.文件内容读取
通过FileReader构造函数创建的实例去访问本地的文件内容,并获取文件的其他信息
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件内容读取</title>
</head>
<body>
<input type="file" id="fileInput">
<div id="box"></div>
<script>
let fileInput = document.querySelector('#fileInput')
let box = document.querySelector('#box')
fileInput.addEventListener('change',(event)=>{
console.log(event.target.files,'文件')
let file = event.target.files[0]
let reader = new FileReader()
if(/image/ .test(file.type)){
reader.readAsDataURL(file)
}else{
reader.readAsText(file)
}
reader.onprogress = (event)=>{
if(event.lengthComputable){
console.log(event,'???')
}
}
reader.onload = (event)=>{
box.innerText = event.target.result
}
})
</script>
</body>
</html>

25.页面访问状态
页面访问状态可以根据document.visibilityState这个属性去访问,visibilitychange事件可以检测到这个状态的更改
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面访问状态</title>
</head>
<body>
<script>
window.addEventListener('visibilitychange',()=>{
console.log(document.visibilityState,'页面状态')
if(document.visibilityState === 'hidden'){
console.log('离开页面')
}else if(document.visibilityState === 'visible'){
console.log('访问页面')
}else if(document.visibilityState === 'visible'){
console.log('预渲染页面')
}
})
</script>
</body>
</html>

26.流
通过ReadableStream构造函数的实例创建可读流
通过WritableStream构造函数的实例创建可写流
1.可读流
js
async function * ints() {
// 每1000 毫秒生成一个递增的整数
for (let i = 0; i < 5; ++i) {
yield await new Promise((resolve) => setTimeout(resolve, 1000, i));
}
}
//可读流
const readableStream = new ReadableStream({
async start(controller) {
for await (let chunk of ints()) {
controller.enqueue(chunk);
}
controller.close();
}
});
const reader = readableStream.getReader();
(async function() {
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(value); // 输出 0, 1, 2, 3, 4,每隔 1 秒输出一个
}
})();

2.可写流
js
async function * ints() {
// 每1000 毫秒生成一个递增的整数
for (let i = 0; i < 5; ++i) {
yield await new Promise((resolve) => setTimeout(resolve, 1000, i));
}
}
//可写流
const writableStream = new WritableStream({
write(value) {
console.log(value,'打印');
}
});
console.log(writableStream.locked);//false
const writableStreamDefaultWriter = writableStream.getWriter();
console.log(writableStream.locked);//true
(async function() {
for await (let chunk of ints()){
await writableStreamDefaultWriter.ready
writableStreamDefaultWriter.write(chunk)
}
writableStreamDefaultWriter.close()
})();

3.转换流
js
async function * ints() {
// 每1000 毫秒生成一个递增的整数
for (let i = 0; i < 5; ++i) {
yield await new Promise((resolve) => setTimeout(resolve, 1000, i));
}
}
const { writable, readable } = new TransformStream({
transform(chunk, controller) {
//controller进行转换的时候将chunk的值都*2
controller.enqueue(chunk * 2);
}
});
const readableStreamDefaultReader = readable.getReader();
const writableStreamDefaultWriter = writable.getWriter();
//消费者
(async function(){
while(true){
const{done, value}=await readableStreamDefaultReader.read();
if(done){
break;
}else{
console.log(value,'读取的流');
}
}
})();
//生产者
(async function(){
for await(let chunk of ints()){
await writableStreamDefaultWriter.ready;
console.log(chunk,'生产的chunk')
writableStreamDefaultWriter.write(chunk);
}
writableStreamDefaultWriter.close();
})();

4.用管道连接流
js
async function * ints() {
// 每1000 毫秒生成一个递增的整数
for (let i = 0; i < 5; ++i) {
yield await new Promise((resolve) => setTimeout(resolve, 1000, i));
}
}
async function * ints() {
// 每1000 毫秒生成一个递增的整数
for (let i = 0; i < 5; ++i) {
yield await new Promise((resolve) => setTimeout(resolve, 1000, i));
}
}
//可读流
const integerStream = new ReadableStream({
async start(controller) {
for await (let chunk of ints()) {
controller.enqueue(chunk);
}
controller.close();
}
});
//转换器
const doublingStream=new TransformStream({
transform(chunk, controller){
controller.enqueue(chunk * 2);
}
});
// 通过管道连接流
const pipedStream= integerStream.pipeThrough(doublingStream);
// 从连接流的输出获得读取器
const pipedStreamDefaultReader = pipedStream.getReader();
// 消费者
(async function() {
while(true) {
const { done, value } = await pipedStreamDefaultReader.read();
if (done) {
break;
} else {
console.log(value,'打印流');
}
}
})();

27.web组件
web组件可以高效的插入DOM,并且不会触发重排
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web组件</title>
<style>
#box{
height: 300px;
background: green;
}
</style>
</head>
<body>
<div>
<button onclick="templateHandler()">组件转移</button>
</div>
<div id="box"></div>
<template id="list">
<p>111</p>
<p>222</p>
<p>333</p>
</template>
<script>
const box = document.querySelector('#box')
const list = document.querySelector('#list')
// DocumentFragment的使用
// const fragment = new DocumentFragment()
// let arr = ['p1','p2','p3']
// arr.forEach((item)=>{
// let p = document.createElement('p')
// p.innerText = `新元素${item}`
// //这样比较高效,为DocumentFragment添加子元素不会导致布局重排
// fragment.appendChild(p)
// })
// box.appendChild(fragment)
// //box在执行完appendChild操作后,fragment就会变成空的
// template的使用
function templateHandler(){
const listFragment = list.content
box.appendChild(document.importNode(listFragment,true))
}
</script>
</body>
</html>

28.影子DOM(了解)
类似于vue中slot的使用
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>影子Dom</title>
</head>
<body>
<script>
//影子DOM的使用,attachShadow创建影子DOM
// for (let color of ['red', 'green', 'blue']) {
// const div = document.createElement('div');
// const shadowDOM = div.attachShadow({ mode: 'open' });
// document.body.appendChild(div);
// shadowDOM.innerHTML = `
// <p>Make me ${color}</p>
// <style>
// p {
// color: ${color};
// }
// </style>
// `;
// }
//使用槽位slot实现
// for (let color of ['red', 'green', 'blue']) {
// const divElement = document.createElement('div');
// divElement.innerText = `Make me ${color}`;
// document.body.appendChild(divElement)
// divElement.attachShadow({ mode: 'open' }).innerHTML = `
// <p><slot></slot></p>
// <style>
// p {
// color: ${color};
// }
// </style>
// `;
// }
//命名slot
document.body.innerHTML = `
<div>
<p slot="foo">Foo</p>
<p slot="bar">Bar</p>
</div>
`;
document.querySelector('div')
.attachShadow({ mode: 'open' })
.innerHTML = `
<slot name="bar"></slot>
<slot name="foo"></slot>
`;
</script>
</body>
</html>
29.自定义元素
通过继承HTMLElement来实现自定义dom,可以像element-ui一样使用元素节点
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义元素</title>
</head>
<body>
<script>
class ElBtnElement extends HTMLElement {
constructor() {
super();
console.log('创建实例');
}
connectedCallback() {
console.log('添加进DOM之中');
}
disconnectedCallback() {
console.log('从Dom中移除');
}
}
customElements.define('el-button', ElBtnElement);
const elBtnElement = document.createElement('el-button');
elBtnElement.style.background = '#1f94ff'
elBtnElement.style.padding = '10px'
elBtnElement.style.color = '#fff'
elBtnElement.style.height = '30px'
elBtnElement.style.cursor = 'pointer'
elBtnElement.style.borderRadius = '8px'
elBtnElement.innerText = '自定义按钮'
//创建实例
document.body.appendChild(elBtnElement);
//添加进DOM之中
setTimeout(() => {
document.body.removeChild(elBtnElement);
//从Dom中移除
}, 3000);
</script>
</body>
</html>

30.fetch请求
fetch请求是比较先进api,不用我们去封装原生的xhmHttpRequest,类似于axios
fetch请求可以自定义请求类型,添加请求头,数据文本化或者json化,如发送请求2
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>fetch请求</title>
</head>
<body>
<div>
<button id="btn" onclick="reqText1()">发送请求1</button>
<button id="btn" onclick="reqText2()">发送请求2</button>
<p id="p1"></p>
</div>
<script>
const p1 = document.querySelector('#p1')
const reqText1 = ()=>{
fetch('http://127.0.0.1:5500/test.txt').then(res=>{
console.log(res,'res的结果')
res.text().then(data=>{
console.log(data)
p1.innerText = data
})
})
}
const reqText2 = ()=>{
const header = new Headers({
'Content-Type':'application/json',
'myToken':'aaabbbcccddd'
})
fetch('http://127.0.0.1:5500/test.json',{method:'GET',headers:header}).then(res=>{
console.log(res,'res的结果')
res.json().then(data=>{
console.log(data)
})
})
}
</script>
</body>
</html>
在本地建两个文件,一个test.txt,一个test.json,开启本地服务

31.工作者线程
js是单线程,但是有些时候我们需要处理一些额外功能的时候,可以使用工作者线程,相当于开了一个子线程,帮你去完成额外的任务。
html文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工作者线程</title>
</head>
<body>
<button class="btn" onclick="sendMsg()">发送消息</button>
<script>
const worker = new Worker('./worker.js')
console.log(worker,'worker实例')
const sendMsg = ()=> {
worker.postMessage('我是长江,收到请回答')
}
worker.onmessage = (data=>{
console.log(data.data,'我是main')
})
</script>
</body>
</html>
work.js
js
self.addEventListener('message',(data)=>{
console.log(data.data,'我是woker')
if(data.data === '我是长江,收到请回答'){
self.postMessage('我是黄河,我收到了')
}
})
开启本地服务
