前端面试3.1-JS

目录

1、缓存

2、观察者模式VS发布订阅模式

 3、防抖和节流

4、闭包

5、可枚举属性

6、Object.assign

7 、数组拍平

8、把平铺的数组结构转成树形结构

9、arr.forEach()与arr.map()

10、轮播图

11、数据类型判断

12、事件循环(Event Loop)

 13、let、var和const

14、原型链

15、this指向情况

16、BOM、DOM

17、事件的传播

18、事件委托

19、for...in和for...of

20、Math.random()


1、缓存

        缓存的优点

  1. 减少不必要的数据传输,减少带宽
  2. 减少服务器负担,提升网站性能
  3. 加快客户端页面加载的速度
  4. 用户体验友好

        缺点:客户端与服务端时间不一致,资源更新会导致客户获取信息滞后

        前端缓存分为HTTP缓存和浏览器缓存 

        HTTP缓存又分为强缓存(本地缓存)和协商缓存

参考:https://blog.csdn.net/weixin_46587293/article/details/123342557

        当浏览器第一次发起请求时(请求头中没有If-Modified-Since)server会在响应中告诉浏览器这个资源最后修改的时间(响应头中的Last-Modified)。当你再次请求这个资源时(在请求头中包含字段 If-Modify-Since),浏览器会询问server这个资源有没有被修改(请求头中If-Modified-Since):

  • 若资源未过期,则返回 304 表示资源未修改
  • 若资源已过期,则返回 200 并附上最新资源以及新的 last-modified

2、观察者模式VS发布订阅模式

        发布订阅是一种消息范式,消息的发送者(发布者)不会将消息直接发送给消息的接收者(订阅者),而是将消息分为不同的类别。无需知道哪些订阅者的存在;同样订阅者也可以表达对一个或多个类别的兴趣,只接受感兴趣的信息,无需了解哪些发布者的存在

        订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

        //事件触发器
        class EventEmitter(){
            constructor(){
                // 初始化对象{ 'click':[fn1,fn2],'change':[fn] }
                this.subs=Object.create(null)
            }
            //注册事件
            $on(eventType,handler){
                this.subs[eventType] = this.subs[eventType] || []
                this.subs[eventType].push(handler)
            }
            //触发事件
            $emit(eventType){
                if(this.subs[eventType]){
                    this.subs[eventType].forEach(handler => {
                        handler()
                    })
                }
            }
        }
        //测试
        
        let em =new EventEmitter()
        
        em.$on('click',()=>{
            console.log('click1')
        })
        em.$on('click',()=>{
            console.log('click2')
        })
        em.$emit('click') //打印结果 click1,click2
  • 观察者模式

        观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。

        观察者模式属于行为性模式,行为型模式关注的是对象间的通讯,观察者模式就是观察者与被观察者之间的通讯

        观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

观察者 --Watcher

update():当事件发生时,具体要做的事情。

发布者 --Dep

subs数组:存储所有的观察者

addSub():添加观察者

notify():当事件发生,调用所有观察者的update()方法

        //发布者-目标
        class Dep{
            constructor() {
                //记录所有的订阅者
                this.subs=[]
            }
            //添加订阅者
            addsub(sub){
                if(sub && sub.update){
                    this.subs.push(sub)
                }
            }
            //发布通知
            notify(){
                this.subs.forEach(sub=>{
                    sub.update()
                })
            }
        }
        //订阅者-观察者
        class Watcher{
            update(){
                console.log('update')
            }
        }
        //测试
        let dep=new Dep()
        let watcher=new Watcher()
        dep.addsub(watcher)
        dep.notify() //打印结果 update
  • 观察者模式与发布订阅的区别
  1. 观察者模式和订阅模式的区别是没有调度中心,只有发布者和订阅者,并且发布者需要知道订阅者的存在
  2. 在发布订阅模式中,组件是松散耦合的,正好与观察者模式相反
  3. 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)
  4. 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

 3、防抖和节流

        防抖和节流都是防止某一事件频繁触发

         防抖是指在指定的单位时间内,如果重复触发了相同的事件,则取消上一次的事件,重新开始计时;将多次执行变为只执行一次

               典型场景:搜索框搜索输入

<body>
  <input id="int" type="text">
</body>
<script>
  const int = document.getElementById("int")

  // 调接口的函数
  const getData = function () {
    console.log("调接口了");
  }

  int.addEventListener('input', double(getData, 1000))

  // 封装防抖函数
  function double(fn, time) {
    let timeout
    return () => {
      // 先清除掉上一次的计时器
      clearTimeout(timeout)
      timeout = setTimeout(() => {
        fn()
      }, time)
    }
  }
</script>

lodash插件解决防抖 

<html lang="en">
    <body>
        <p>
            请你输入你搜索的内容:<input type="text">
        </p>
    </body>
</html>
<script>
    let input=document.querySelector('input')
    // 文本发生变化立即执行
    /* input.oninput = function(){
        console.log("ajax请求");
    } */
    // 用户输入后1s发送请求
    input.oninput = _.debounce(function(){
        console.log("ajax发送请求");
    },1000)
    // lodash插件:里面封装了函数的防抖与节流的业务【闭包+延迟器】
    // 下载:npm i --save lodash
    // 1.lodash数据库对外暴露_函数
    /* console.log(_);
    _.debounce(function(){
        console.log('1秒之后执行一次');
    },1000)() */
</script>

        节流:单位时间内,频繁出发事件,只执行一次

                典型场景:高频事件(快速点击、鼠标滑动、resize事件、scroll事件)

<body>
  <input id="int" type="text">
</body>
<script>
  const int = document.getElementById("int")

  // 调接口的函数
  const getData = function () {
    console.log("调接口了");
  }

  int.addEventListener('input', double(getData, 1000))

  // 封装节流函数
  function double(fn, time) {
    let isLoad = false
    return () => {
      if (!isLoad) {
        // 设置为true后,即禁止下一个计时器执行
        isLoad = true
        setTimeout(() => {
          fn()
          // 等这个计时器执行完毕后,再设置为false,才允许下一个计时器执行
          isLoad = false
        }, time)
      }
    }
  }
</script>

lodash插件解决节流 

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="../node_modules/lodash/lodash.js"></script>
</head>
<body>
    <div>
        <h1>我是计数器<span>0</span></h1>
        <button>点我加一</button>
    </div>
</body>
</html>
<script>
    // 获取节点
    let span = document.querySelector('span')
    let button = document.querySelector('button')
    let count = 0
    // 计数器在一秒内数字只能加一
    /* button.onclick=function(){
        count++;
        span.innerHTML = count
    } */
    button.onclick=_.throttle(function(){
        // 节流:这个回调函数1s执行一次
        count++;
        span.innerHTML = count
    },1000)
</script>

        实现代码防抖与节流的原理_防抖节流原理_小智玩前端的博客-CSDN博客

4、闭包

        闭包是指有权访问另一个函数作用域中的变量的函数;使用闭包主要是为了设计私有的方法和变量;

       闭包的特性

  • 函数嵌套函数
  • 可以让函数的外部访问到函数内部的局部变量
  •  参数和变量不会以垃圾回收机制回收

        优点:可以避免使用全局变量,防止全局变量污染        

        缺点:可能造成内存泄露或溢出

        因为js有自动的垃圾回收机制,当变量不被引用时就会通过特殊的算法回收(一般是标记清除和引用计数两种方法),当一个函数执行完后作用域会被销毁,而闭包可以访问父级作用域,父级函数中的变量在闭包中被引用,那么就不会被垃圾回收;所以闭包可以实现在函数外部访问内部的局部变量,并且可以使让这些变量的值始终保持在内存中。

       var a=10
       function fun() {
            console.log(a);
       }
       function foo(fun){
            var a = 20;
            (function(){
                fun()
            })()
       }
       foo(fun)//10
       function fun() {
            let a = 1
            function foo(){
                return ++a
            }
            return foo
       }
       var b = fun()
       console.log(b());//2
       console.log(b());//3 闭包中的变量不会释放

5、可枚举属性

        Object.defineProperty()方法中enumerable配置项

        可枚举的属性可以通过for...in循环进行遍历(除非该属性名是一个Symbol),或者通过Object.keys()方法返回一个可枚举属性的数组。

        js中的基本包装类型的原型属性是不可枚举的,比如Object,Array,Number等

        function disney(name,sex){
            this.name=name
            this.sex=sex
        }
        let Qibao1=new disney("Dafu","male")
        Object.defineProperty(Qibao1, "age", {
            value: 18,
            //是否为枚举属性
            enumerable: false
        }); 
        for (p in Qibao1){
            console.log(p);  //name sex
        }
        let Qibao2=new disney("Linabell","female")
        Object.defineProperty(Qibao2, "age", {
            value: 6,
            enumerable: true
        }); 
        console.log("########");
        for (q in Qibao2){
            console.log(q);  //name sex age
        }

6、Object.assign

        用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,且返回目标对象。

                1)属性重名时,源对象中的属性值覆盖目标对象中的属性值

                2)源对象可以是多个

        作用:Object.assign可以实现对象的合并。

        语法:Object.assign(target, ...sources)

        let Qibao1=new disney("Dafu","male")
        let Qibao2=new disney("Linabell","female")
        Object.defineProperty(Qibao2, "age", {
            value: 6,
            enumerable: false
        });
        console.log(Object.assign(Qibao1,Qibao2));

控制台输出:

        let Qibao1=new disney("Dafu","male")
        let Qibao2=new disney("Linabell","female")
        Object.defineProperty(Qibao2, "age", {
            value: 6,
            enumerable: true
        });
        console.log(Object.assign(Qibao1,Qibao2));

 控制台输出:

 将两个或多个对象的属性合并到一起,不改变原有对象的属性,可以用一个空的对象作为target对象。

console.log(Object.assign({},Qibao1,Qibao2));

控制台输出:

7 、数组拍平

        多维数组扩展为一维数组并返回

        1.Array.prototype.flat()       (flat方法返回的是新数组)

const arr = ["????", ["????", "????"], ["????", ["????", ["????"]], "????"]];
 
// 不传参数时,默认“拉平”一层
arr.flat();
// ["????", "????", "????", "????", ["????", ["????"]], "????"]
 
// 传入一个整数参数,整数即“拉平”的层数
arr.flat(2);
// ["????", "????", "????", "????", "????", ["????"], "????"]
 
// Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
arr.flat(Infinity);
// ["????", "????", "????", "????", "????", "????", "????"]
 
// 传入 <=0 的整数将返回原数组,不“拉平”
arr.flat(0);
arr.flat(-10);
// ["????", ["????", "????"], ["????", ["????", ["????"]], "????"]];
 
// 如果原数组有空位,flat()方法会跳过空位。
["????", "????", "????", "????",,].flat();
// ["????", "????", "????", "????"]

        2.展开运算符+arr.some()

        function flatter(arr){
            while(arr.some(item=>Array.isArray(item))){
                arr = [].concat(...arr)
            }
            return arr
        }
        arr = [1,2,3,[4,5,[6,7]],8,9]
        console.log(flatter(arr));//[1,2,3,4,5,6,7,8,9]

        3.toString+str.split

        function flatter(arr){
            return arr.toString().split(',').map(item=>{
                return +item  //转换为数字类型
            })
        }
        arr = [1,2,3,[4,5,[6,7]],8,9]
        console.log(flatter(arr))

        4.递归

        function flatter(arr){
            let res = []
            arr.forEach(item => {
                if(item instanceof Array){
                    res = res.concat(flatter(item))
                }else{
                    res.push(item)
                }
            });
            return res
        }
        arr = [1,2,3,[4,5,[6,7]],8,9]
        console.log(flatter(arr))

         5.arr.reduce+递归

        reduce方法:arr.reduce(callback, initialValue) 

                reduce为数组中的每个元素依次执行回调函数,接受4个参数:初始值(或者上次回调函数的返回值)、当前元素值、当前索引、调用reduce的数组。

                 initialValue为第一个参数的初始值,未设置初始值时默认为数组的第一个元素

        1)reduce实现数组累加/累乘

        arr = [2,34,2.2,34,3]
        console.log(arr.reduce((pre,cur)=>pre+cur));

        2)reduce找出数组中的最大/最小值 

        arr = [2,34,2.2,34,3,2,3]
        console.log(arr.reduce((pre,cur)=> pre>cur?pre:cur));//最大值
        console.log(arr.reduce((pre,cur)=> pre<cur?pre:cur));//最小值

        3)reduce实现数组元素个数统计 

        arr = [2,34,2.2,34,3,2,3]
        console.log(arr.reduce((pre,cur)=>{
            pre[cur] = pre[cur]+1 || 1
            return pre
        },{}));//{2: 2, 3: 2, 34: 2, 2.2: 1}

         4)reduce实现数组去重 

        arr = [2,34,2.2,34,3,2,3]
        console.log(arr.reduce((pre,cur)=> {
           if(!pre.includes(cur)){
                pre.push(cur)
           }
           return pre
        },[]));

         5)reduce实现数组拍平

        function flatter(arr){
            return arr.reduce((pre,cur)=>pre.concat(cur instanceof Array ? flatter(cur):cur),[])
        }
        arr = [1,2,3,[4,5,[6,7]],8,9]
        console.log(flatter(arr))

8、把平铺的数组结构转成树形结构

        第一种

       1) 给数组的每个元素都加上children用于存储二级数组

              创建一个对象map,元素id为key,元素本身为value;便于对应关系

        2)判断每个元素的pid:若pid不存在,说明为一级数组,直接push进最终输出数组

                                                若pid存在,则为二级数组,push进pid对应的children属性中

(由于数组中的元素和map中都为引用类型,所以操作map中的元素数组中的元素也会发生变化)

<script>
	/**
	* 把平铺的数组结构转成树形结构
	*/
	const arr = [
		{ 'id': '29', 'pid': '',     'name': '总裁办' },
		{ 'id': '2c', 'pid': '',     'name': '财务部' },
		{ 'id': '2d', 'pid': '2c', 'name': '财务核算部'},
		{ 'id': '2f', 'pid': '2c', 'name': '薪资管理部'},
		{ 'id': 'd2', 'pid': '',     'name': '技术部'},
		{ 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部'}
	]
    function tranListToTreeData(list){
        let treeData = [],map = {};
        list.forEach(item => {
            if(!item.children){
                item.children = []
            }
            map[item.id] = item
        });
        list.forEach(item=>{
            if(item.pid){
                map[item.pid].children.push(item)
            }else{
                treeData.push(item)
            }
        })
        return treeData;
    }
    let res = tranListToTreeData(arr)
    console.log(res);
    // 因为都是引用数据类型,所以原数组中的值也会发生变化
</script>

        输出结果:

 第二种:递归

                先筛选出一级数组的元素

                再筛选pid与一级数组id相同的元素加入该一级数组的children属性

    function findChildren(list,id){
        let treeList = []
        treeList = list.filter(item=>item.pid===id)
        treeList.forEach(item => {
            item.children = findChildren(list,item.id)
        });
        return treeList
    }
    let res = findChildren(arr,'')
    console.log(res);

9、arr.forEach()与arr.map()

         forEach()  针对每一个元素执行提供的函数;返回结果为undefined  不能中断(break和return) ;修改原来的数组

        map()  创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数的来;返回结果为修改后的新数组  map()的执行速度优于forEach()

        callback函数都接收当前元素item,当前元素下标index,数组本身arr

                 arr.forEach((item,index,arr)=>{})

        l两种方法都不能用break中断,否则会引发异常;

        map和forEach抛出throw new error()通过try catch去捕获这个错误来终止循环

let list=[1,2,3,4,5,6];
try{
  list.map(item=>{
     if(item===3){
          throw new Error()
         }
     console.log(item)
  })
} catch {}
// 1 2

10、轮播图

        1)DOM实现 

                将图片的url存入数组   开启定时器

	var img1 = document.getElementById("img1");
	//创建一个数组来保存图片的路径
	var imgArr = ["../img/甜甜/2013/2013.jpg","../img/甜甜/2013/2013-1.jpg",……];
	//创建变量保存当前图片的索引
	var index = 0;
	//定义变量 保存定时器标识
	var timer;
	var btn01 = document.getElementById("btn01");
	btn01.onclick = function(){
		/*
			* 目前 每点击一次按钮就会开启一个定时器
			* 	点击多次就会开启多个定时器,导致图片的切换速度过快
			* 	并且我们只能关闭最后一次开启的定时器
		*/
		//在开启定时器前,需要将当前元素上的其他定时器关闭
		clearInterval(timer);
		/*
			* 开启定时器 自动切换图片
		*/
		timer = setInterval(function(){
			//使索引自增
			index++;
			//判断索引是否超过最大索引
			index %= imgArr.length;
			//修改img1的src属性
			img1.src = imgArr[index];
		},1000);
	};

         2)swiper插件

                swiper插件使用要保证页面中数据和结构都完整

                swiper-->slide grid(网格分布)-->slidesPerView  设置轮播图一次展示多少张图片

    <div class="swiper-container" id="floor1Swiper" ref="cur">
        <div class="swiper-wrapper">
            <div class="swiper-slide" v-for="carousel in list" :key="carousel.id">
                <img :src="carousel.imgUrl" />
            </div>
        </div>
        <!-- 如果需要分页器 -->
        <div class="swiper-pagination"></div>

        <!-- 如果需要导航按钮 -->
        <div class="swiper-button-prev"></div>
        <div class="swiper-button-next"></div>
    </div>

        如果在mounted中使用,数据请求已经完成,但不能保证v-for循环完成,即结构不一定完整

        解决方案一:结合延时器

     setTimeout(() => {
       var mySwiper = new Swiper(this.$refs.cur, {
         // direction: 'vertical', // 垂直切换选项 默认为水平方向
         loop: true, // 循环模式选项
 
         // 如果需要分页器
         pagination: {
           el: '.swiper-pagination',
           clickable :true,//点击小球(分页器)也能切换图片
         },
 
         // 如果需要前进后退按钮
         navigation: {
           nextEl: '.swiper-button-next',
           prevEl: '.swiper-button-prev',
         },
       },2000)
     }) 

        解决方案二: nextTick(DOM节点更新后执行回调)

<script>
import Swiper from 'swiper';
export default {
    name: 'Carousel',
    props:['list'],
    watch: {
    list: {
      immediate: true, //立即监听 无论数据有无变化 先监听一次 否则监听不到list的变化 因为list由父亲传输 不会发生变化
      handler() {
        // 只能监听到数据已经有了 但v-for动态渲染的情况不能确定
        this.$nextTick(() => {
          var mySwiper = new Swiper(this.$refs.cur, {
            // direction: 'vertical', // 垂直切换选项 默认为水平方向
            loop: true, // 循环模式选项

            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
              clickable: true,//点击小球(分页器)也能切换图片
            },

            // 如果需要前进后退按钮
            navigation: {
              nextEl: '.swiper-button-next',
              prevEl: '.swiper-button-prev',
            }
          })
        })
      }
    }
  }
}

11、数据类型判断

        1)typeof

                返回表示数据类型的字符串,返回结果包括:number、string、boolean、object、undefined、function

                null/数组/实例对象:返回object

        JS中小数计算精度问题:细说JavaScript中小数点计算不精准的原因和解决方案

        2)instanceOf

                A instanceOf B  判断A是否为B的实例

                用来判断两个对象是否属于原型链关系,不能获取对象的具体类型

        3)constructor

                当一个函数F被定义时,JS引擎会为F添加prototype原型,在prototype上添加constructor属性,并让其指向F的引用;当执行var f = new F()时,F被当作构造函数,F原型的constructor传递到f身上 f.constructor == F

                问题:null和undefined是无效对象,不会有constructor

                          当开发者重写prototype后,原有的constructor消失,默认为Object

        4)Object.prototype.toString()

       let num = 123
       console.log(num.toString());//123
       console.log(Object.prototype.toString.call(num));//[object Number]
       console.log(num.toString.call(num));//123

                必须通过Object.prototype.toString.call来获取,而不能直接 obj.toString()。因为大部分的对象都实现了自身的toString方法,这样就可能会导致Object的toString被终止查找,因此要用call来强制执行Object的toString方法。

        5)===

                可以判断undefined和null

       let a
       console.log(a === undefined);//true 定义未赋值
       let b = null
       console.log(b === null);//true 定义且赋值为null

        ==:值相等(如果两边操作数类型不同,会先做隐式类型转换)

        ===:值和类型都相等

        Object.is():+0和-0不等;NAN=NAN

        console.log(-0 === +0);//true
        console.log(-0 == +0);//true
        console.log(Object.is(-0,+0));//false
        console.log(NaN == 0/0);//false
        console.log(NaN === 0/0);//false
        console.log(Object.is(NaN,0/0));//true

12、事件循环(Event Loop)

  •         线程和进程

                        进程:计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行分配资源和调度的基本单位

                        线程:操作系统能够进行运算调度的最小单位

                        一个进程中可以并发多个线程,每条线程并行执行不同的任务

        JavaScript语言是单线程的

        浏览器这个进程中有许多线程,其中一个是JS引擎线程,还有一个GUI渲染线程;这两个线程是互斥的 

        JS引擎线程是基于事件循环来执行的  解决了JS中单线程阻塞问题

        JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列来搞定另一些代码的执行,整个执行过程,我们称为事件循环过程

        JS中所有任务可以分为两种:同步任务    异步任务

                同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

                异步任务:不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程某个异步任务可以执行了,该任务才会进入主线程执行

                       异步任务又分为:宏任务(macro-task)        微任务(micro-task)

                                   常见宏任务:script(整块代码)  setInterval()  setTimeout()  setImmediate()  ajax  事件绑定   I/O   UI render 

                                   常见微任务:new Promise()后的then和catch函数(Promise本身是同步的)   async/await       new MutationObserver()  process.nextTick(Nodejs)

                                   若同时存在promise和nextTick,则先执行nextTick

                任务队列中微任务和宏任务同时存在时,先执行微任务再执行宏任务;

setTimeout(() => {
    console.log('异步1任务time1');
    new Promise(function (resolve, reject) {
        console.log('异步1宏任务promise');
        setTimeout(() => {
            console.log('异步1任务time2');
        }, 0);
        resolve();
    }).then(function () {
        console.log('异步1微任务then')
    })
}, 0);
console.log('主线程宏任务1');
setTimeout(() => {
    console.log('异步2任务time2');
 
}, 0);
new Promise(function (resolve, reject) {
    console.log('宏任务promise');
    // reject();
    resolve();
}).then(function () {
    console.log('微任务then')
}).catch(function () {
    console.log('微任务catch')
})
console.log('主线程宏任务2');

输出:主线程宏任务1----宏任务promise----主线程宏任务2----微任务then----异步1任务time1----异步1宏任务promise----异步1微任务then----异步2任务time2----异步1任务time2

        async/await执行顺序

                async会将其后函数返回值封装为一个Promise对象,而await会等待这个Promise完成,并将其resolve的结果返回出来

                async函数在遇到await后会立即返回Promise对象,继续执行async函数外部逻辑,async函数内部会被await阻塞

console.log('script start')
 
async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
}
async1()
 
setTimeout(function() {
    console.log('setTimeout')
}, 0)
 
new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function() {
    console.log('promise1')
})
.then(function() {
    console.log('promise2')
})
 
console.log('script end')

        输出:async2 end----Promise----script end----async1 end----promise1----promise2----setTimeout

 13、let、var和const

        1)var:使用var操作符定义的变量会变成包含它的函数的局部变量

                变量提升:var关键字定义的变量会自动提升到函数作用域顶部

                var可以重复声明

       2) let、const:不存在变量提升

                                不可以重复声明

                                let/const声明的范围是块作用域,var声明的范围是函数作用域

        let在全局作用域中声明的变量不会成为window的属性,var声明的变量则会

var num1=20
console.log(window.num1);//20
let num2=10
console.log(window.num2);//undefined

         let和var的for循环

for (let i = 0; i < 10; i++) {
    setTimeout(()=>{
        console.log('let'+i);//0,1,2,3……9
    },0)
}
for (var i = 0; i < 10; i++) {
    setTimeout(()=>{
        console.log('var'+i);//10,10,10……10
    },0)
}

        setTimeout是异步任务,每一次for循环时,setTimeout都执行一次,但里面的函数没有被执行,而是被放到任务队列中等待执行;只有主线上的任务执行完毕,才会执行任务队列中的任务,也就是等for循环执行完毕,才会执行setTimeout的回调函数

        对于let:let不仅将i绑定到for循环中,它将i重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值被重新赋值,即每次迭代过程中i都是独立的存在。setTimeout的回调函数属于一个新的作用域

        对于var:此时i值已经变为10

        解决var声明变量,连续输出相同数的问题:闭包

for (var i = 0; i < 10; i++) {
    (function (j) {
        setTimeout(function timer() {
            console.log(j);
        }, 0);
    })(i);
}

        通过闭包将i的变量驻留在内存中。

       3) const:声明一个只读的常量,声明时必须初始化(赋值);一旦声明常量的值不能改变(栈中的值不能变;引用类型的内存地址不能变,值可以变)

let arr = []
const a = arr
arr[0]=1
console.log(a);//[1]

14、原型链

        所有对象都有一个__proto__(隐式原型)属性 在创建对象时自动添加,默认值是构造函数的prototype对象

        所有函数都有一个prototype(显式原型)属性  在定义函数时自动添加,默认值是一个空Object对象

    

         原型链:

                访问一个对象属性时:先在自身属性中查找,找到返回;如果没有,再沿着__proto__这条链向上查找,找到返回;如果最终没找到,返回undefined

         Object的原型对象是原型链的尽头

        console.log(Object.prototype.__proto__);//null

        所有函数都是Function的实例 

  var F = function(){};
  Object.prototype.a = function(){
    console.log('a()')
  };
  Function.prototype.b = function(){
    console.log('b()')
  };
  var f = new F();
  f.a()  //a()
  f.b()  //报错  F.prototype是个对象 所以其构造函数为Object
  F.a()  //a()
  F.b()  //b()  
        console.log(f instanceof Function);//false
        console.log(F instanceof Function);//true
        console.log(f instanceof Object);//true
        console.log(F instanceof Object);//true
        console.log(Object instanceof Function);//true
        console.log(Function instanceof Object);//true

15、this指向情况

        1)当以函数形式调用时,this指向window【test()】

        2)当以方法形式调用时,this指向调用方法的对象【p.test()】

        3)当以构造函数形式调用时,this指向新创建的对象【new test()】

        4)使用call和apply调用时,this指向指定的对象【p.call(obj)】

16、BOM、DOM

        JavaScript由三个部分组成:

                EMCAScript:描述了JS的语法和基本对象

                BOM(浏览器对象):与浏览器交互的方法和对象

                DOM(文件对象模型):处理网页内容的方法和接口

    1) BOM(Browser Object Model) 浏览器对象模型,BOM可以使我们通过JS来操作浏览器

        在BOM中为我们提供了一组对象来完成对浏览器的操作

        BOM对象:

         * Window 代表的是整个浏览器窗口,同时window也是网页中的全局对象

         * Navigator 代表当前浏览器的信息,通过该对象可以来识别不同的浏览器

         * Location 代表当前浏览器的地址栏信息,通过Location可以获取地址栏信息或者操作浏览器跳转页面

         * History 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录;由于隐私的原因,该对象不能获取具体的历史记录,只能操作浏览器向前或向后;该操作只在档次访问时有效

         * Screen 代表用户的屏幕信息,通过该对象可以获取到用户的显示器的相关的信息

     2)DOM (Document Object Model)文档对象模型,JS通过DOM来对HTML文档进行操作

            一个网页就是一个文档    将网页的每一部分都转换为一个对象

        DOM是W3C的标准,所有浏览器共同遵守的标准

        BOM是各个浏览器厂商根据DOM在各自浏览器上的实现(表现为不同浏览器定义有差别、实现方式不同)

        window为BOM对象,而非JS对象 

参考:DOM和BOM的区别

17、事件的传播

-W3C综合了两个公司的方案,将事件的传播分成了三个阶段

        * 1.捕获阶段:从最外层的祖先元素向目标元素进行事件的捕获,默认此时不会触发事件

        * 2.目标阶段:事件捕获到目标元素,开始在目标元素上触发事件

        * 3.冒泡阶段:事件从目标元素向它的祖先元素传递,依次触发祖先上的元素

                //取消冒泡:cancelBubble设置为true即可取消冒泡

*如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数改为true(一般情况下不用

18、事件委托

        利用事件冒泡,将子元素的事件都绑定到父元素身上

        好处:1)替代循环绑定事件的操作,减少内存消耗,提高性能

                    2)简化了DOM节点更新时,相应事件的更新

        缺点:1)事件委托基于冒泡,对于不冒泡的事件不支持

                   2)层级过多,冒泡过程中,可能会被某层阻止掉

                   3)理论上委托导致浏览器频繁调用处理函数,虽然可能不需要处理,所以建议就近委托

19、for...in和for...of

        for...in保存键名                                  for...of保存键值

    let arr = ['linabell','gelatoni','dafu']
    for (const key in arr) {
        console.log(key);//0 1 2
    }
    for (const value of arr) {
        console.log(value);//linabell  gelatoni  dafu
    }

20、Math.random()

<script>
    console.log(Math.random());//生成[0,1)随机数
    console.log(Matn.random()*10);//生成[0,10)随机数
</script>

        Math.random()*(max-min)+min:生成[min,max)之间的随机数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值