js读书笔记(补充知识)

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('我是黄河,我收到了')
    }
})

开启本地服务

相关推荐
onejason2 分钟前
如何利用爬虫获取腾讯新闻详情数据:实战指南
前端·python
用户711607545786 分钟前
LAYA引擎WebGL上下文丢失与恢复处理机制
前端
酷爱码9 分钟前
UNIAPP圈子社区纯前端万能源码模板 H5小程序APP多端兼容 酷炫UI
前端·小程序·uni-app
一朵好运莲13 分钟前
Next+React项目启动慢刷新慢的解决方法
前端·react.js·前端框架
EasyCVR22 分钟前
JavaScript API与WebRTC技术解析:EasyRTC嵌入式视频通话SDK的实现
javascript·音视频·webrtc
唐诗26 分钟前
这位同学说一说 vue3 的 Provide、Inject
前端·github
zoomdong35 分钟前
10x 提升!TypeScript 宣布使用 Go 重写
前端·typescript
东风西巷36 分钟前
Rubick:基于Electron的开源插件化桌面工具箱
前端·javascript·electron·软件需求
离歌漠37 分钟前
electron+vue+webview内嵌网页并注入js
javascript·vue.js·electron
数据潜水员1 小时前
重构及封装
javascript·windows·重构