前端(html, css, js)基础总结

172 阅读24分钟

HTML

什么是HTML语义化
    html语义化**就是让页面的内容结构化,便于对浏览器、搜索引擎解析**;在没有样式CCS情况下也以一种文档格式显示,并且是容易阅读的。 搜索引擎的爬虫依赖于标记来确定上下文和各个关键字的权重,利于SEO。 使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解
    
    总结: 
    根据内容的结构化(内容语义化),选择适合的标签(代码语义化)
    1.有利于搜索引擎更好的解析和进行爬虫(SEO2.有助于开发者方便阅读,维护和写出更优雅的代码
    3.在没有css的情况下,也能呈现出内容的结构和代码的结构
什么是HTML5,以及和HTML的区别是什么
    什么是html5:
    html5 是html的第五次修改,是w3c的最新标准,其目的是将互联网语义话,方便机器和人阅读,更好的支持各种媒体的嵌入,html5 是向后兼容
    
    和HTML的区别:
    1. 文档的声明不一样
        (1) html 声明
            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
            //或
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
            
        (2) html5 声明
            <!DOCTYPE html>
            html5 的声明简洁,有助于开发人员阅读和开发
    2. 结构化语义不一样
        html: 没有体现出结构化语义 如: <div id='xxx'></div>
        html5: 添加了很多语义化标签,如 header, aside, article, vudio, audio等
        
    3. 绘图不一样
        html: 矢量图形
        html5: 可以使用canvas 进行绘图
        
    4. 音频和视频支不支持
        html:不支持
        html5: 支持
        
    5. 语法的处理
        html: 不会处理不准确的语法
        html5: HTML5能够处理不准确的语法
简单描述从输入网址到页面现实的过程
    1. DNS 域名解析
    2. TCP 连接
        深挖(3次握手,4次挥手)
    3. 发送HTTP请求
    4. 服务器响应请求,并返回http 报文
    5. 浏览器解析渲染页面
      (1)解析HTMl 生成dom树
      (2)解析CSS 生成CSSom 树
      (3)合并生渲染树
      (4)浏览器开始绘制和渲染页面
    6. 断开链接
简述html页面渲染的过程
    浏览器解析html 是‘自上而下’加载,并在加载中进行解析和渲染
    1.浏览器解析Dom生成一个dom树,深度遍历当前子树,如果当前节点所有子树结构完成,才遍历当前下一个兄弟节点树
    2.css 解析成css rule tree
    3.根据dom tree 和 cssom tree 来构成 rendering tree
    4.根据rendering tree, 浏览器知道每个节点和每个节点的从属关系,然后计算出每个节点在屏幕展示的位置
 
回流和重绘是什么,什么场景下会触发
    1.回流: 通过渲染树计算出元素在可视化窗口的大小和位置,这个过程就回流
    2.重绘: 再经过渲染树和回流,最终显示在屏幕上的实际像素,这个阶段叫重绘
    
    
    回流触发:
    1. 删除或者添加元素
    2. 浏览器的窗口尺寸发生了变化
    3. 元素位置发生了变化
    4. 元素的尺寸发生了变化(如:padding, border, height, width 等)
    
    重绘触发:
    
    1. 颜色修改
    2. 阴影修改
    
    
CSSOM树和DOM树是同时解析的吗?
 浏览器会下载HTML解析页面生成DOM树,遇到css标签 会解析成CSSOM,这个过程是不会造成阻塞的,但是如果遇到了js脚本,假如CSSOM 还没有构造完成,会等CSSOM构造完成,在执行js脚本,然后在进行DOM 解析,此过程会造成阻塞Dom渲染阻塞
script 标签中,async和defer 有什么区别
    相同点:都是异步加载(fetch)
    
    不同点:
        async加载完成后,会立即执行(execution)
        
        defer加载完成后延迟到dom解析完成之后执行(execution),但会在事件DomContentLoaded之前执行
什么是DOM,BOM
DOMDocument Object Model (文档对象模版)
    处理网页内容的方法和接口,是W3C的标准
    如:删除,添加元素等
BOMBrower Object Model (浏览器对象模版)
    可以简单理解为控制浏览器的行为和接口, 是浏览器厂商根据Dom在各个浏览器上的实现
    如:前进,后退,跳转到另一个页面,获取屏幕大小等
    
 补充:   
    
 BOM的核心就是windowBOM包含了(DOM)对象,浏览器提供了访问BOM的方法,所以我们能通过BOM访问到Dom对象上
    
浏览器的内核
    1IE浏览器内核:Trident内核,也是俗称的IE内核;
    2Chrome浏览器内核:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核;
    3Firefox浏览器内核:Gecko内核,俗称Firefox内核;
    4Safari浏览器内核:Webkit内核;
    5Opera浏览器内核:最初是自己的Presto内核,后来是Webkit,现在是Blink内核;
    6360浏览器、猎豹浏览器内核:IE+Chrome双内核;
    7、搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
    8、百度浏览器、世界之窗内核:IE内核;
    92345浏览器内核:以前是IE内核,现在也是IE+Chrome双内核;
https 和 http 的区别
http:超文本传输协议,
    简单理解: 客户端和服务器端相互传输消息

https: 超文本传输安全协议,在http 的基础上添加了一个 ssl(安全加密隧道协商完成)
    简单理解:在客户端和服务器端相互传输消息的过程中,进行了数据加密,保护了数据的隐私性和完整性
    
 区别:
 1.http是明文,https是进行数据加密的
 2.https需要证书
 3.http比https 速度更快
 4.https比http 更消耗资源
http 强缓存和协商缓存
    htt缓存分为协商缓存和强缓存
    强缓存大于协商缓存
    
    1. 判断是否有强缓存
        expires(http1.0): 绝对时间
        cache-control(http1.1) :  
            max-age : xx秒(时间戳)
            no-cache 
            no-store:  不使用缓存
            public/private: 是否只能被单个用户使用
      通过cache-control和expires 来判断是否被强缓存(cache-control优先级比expires高)
      
     2,判断是否为协商缓存
       ETag/ If-None-Match
       
       Last-Modified/ If-Modified-Since
       
       
       ETag 优先级高于Last-Modified
       
       (1)服务器设置是否缓存
           eg. const express = require('express');\
               const app = express();\
               var options = { \
                  etagtrue// 开启协商缓存\
                  lastModifiedtrue// 开启协商缓存\
                  setHeaders(res, path, stat) => {\
                    res.set({\
                      'Cache-Control''max-age=00'// 浏览器不走强缓存\
                      'Pragma''no-cache'// 浏览器不走强缓存\
                    });\
                  },\
                };\
                app.use(express.static((__dirname + '/public'), options));\
                app.listen(3001);
      (2) 第一次请求 responese headers 会返回ETag(hash值),或者last-Modified(最后一次修改的值)
      (3) 如果responese headers返回的结果的时候有ETag(hash值),再次请求的时候request headers 会通过 If-None-Match 携带ETag(hash值),服务端会判断If-None-Match 携带ETag与当前的hash值是否一致
          一致,返回304,读取浏览器缓存。
          不一致,获取新的资源
          
      (4)如果responese headers返回的结果的时候没有ETag(hash值),会去判断上次请求的Last-Modified是否存在,存在 ==〉再次请求的时候,会判断 If-Modified-Since 携带Last-Modified 与服务端的是否一致
          一致,返回304,读取浏览器缓存。
          不一致,获取新的资源
       

参考文章

CSS

js 和 css 是如何影响Dom树的构建的
    1.html是自上而下加载
    2.解析页面生成dom树,遇见css标签会进行下载和解析
    3.css的下载和解析不会影响dom的解析,但是会阻塞dom的渲染
        我的理解:如果不不阻塞的话,会造成回流或者重绘
    4.如果js放在css标签下面,css解析完成过后,才会去执行js,执行js也会阻塞dom的渲染,js执行完成才回去渲染dom
        理解:同样会造成回流或者重绘
       
css垂直居中有哪些实现方法

      <div id="parent">
        <p id="child"></p>
      </div>
      
        #parent {
          width: 100vw;
          height: 100vh;
        }

        #child {
          width: 200px;
          height: 200px;
          background-color: aqua;
        }
        
        
     分为3种情况
     1.已知子元素宽高
     
     2.已知父元素宽高
     
     3.未知子元素宽高
        
        
     /* 已知子元素宽高 */
      /* 1. 使用绝对定位和margin 子元素设置  */
      /* position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      margin: auto; */


      /* 2. 绝对定位 加 margin负值 子元素设置  */
      /* position: absolute;
      top: 50%;
      left: 50%;
      margin-left: -100px;
      margin-top: -100px; */

      /* 3. 绝对定位加calc 子元素设置  */
      /* position: absolute;
      top: calc(50% - 100px);
      left: calc(50% - 100px); */



      /* 已知父元素宽高 */
      /* 1.子元素设置    
       display: inline-block;
       vertical-align: middle;
       父元素设置
       line-height: 500px;
       text-align: center; */

      /* 2.父元素设置
      display: table-cell;
      text-align: center;
      vertical-align: middle;

      子元素设置
      display: inline-block; */


      /* 未知宽高 */
      /* 1. 绝对定位 加 transform 子元素设置  */
      /* position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) */


      /* 2. flex布局    父元素设置 */
      /* display: flex;
      justify-content: center;
      align-items: center; */


      /* 3. flex 加 magin auto */
      /* 父元素设置
      display: flex;
      子元素设置
      margin: auto; */


      /* 4.grid 网格布局 父元素设置*/
      /* display: grid;
      align-items: center;
      justify-content: center; */

CSS中的flex: 1; 是什么意思
    flex: 1 === flex: 1 1 auto
    
    flex:initail === flex: 0 1 auto
    
    flex-frow:用于设置各item项按对应比例划分剩余空间,flex-grow默认是0eg: 若各个item都是flex-grow = 1; 则等分剩余的空间。
        若其中一个flex-grow = 2; 代表该item占剩余空间的2份,其余 占1份
    flex-shrink:用于设置item的总和超出容器空间时,各item的收缩比例,默认为1。
    eg. 若各个item都是flex-shrink = 1, 则等比例缩小。
        若其中flex-shrink = 0,则该item不缩小,其它item等比例缩小  
    flex-basic:在分配多余空间之前,占据主轴的空间(main size),浏览器根据这个属性计算出是否有剩余的空间,默认值为 auto,即item的本身的大小
    
什么是BFC
  1. bfc是什么

  2. bfc触发条件

  3. bfc的应用场景

    bfc是什么?
    
    Block Formatting Context (块级格式化上下文) ,他是一个页面的渲染区域,有一套自己的规则
    1. 内部的盒子自上而下一个一个的垂直放置
    2. 同一个bfc的2个相邻的盒子会发生重叠(不论什么方向)
    3. 每个元素的左外边距与包含块的左边距相接触(从左到右),浮动元素 也是如此
        我的理解:bfc盒子内的元素只能达到盒子最左边,也就是盒子的左边距(浮动元素也包含在这里面) 不包含的:  position: absolute
    4. 计算bfc高度时,浮动元素也会被计算
    5. 浮动元素不会和bfc元素发生重叠
    6. bfc是一个页面独立的渲染区域,不会与外界元素发生关系,反之亦然
    bfc的触发条件
    
    1. 根元素
    2. overflow: hidden, scroll, auto(除了visible的元素)
    3. float: left, right,(除了none)
    4. display: tabel,grid, flex, inline-block 等
    5. position: absolute, fixed
    bfc 的应用场景
    
    1. margin发生重叠的案列
    <style>
        p {
            color: #f55;
            background: #fcc;
            width: 200px;
            line-height: 100px;
            text-align:center;
            margin: 100px;
        }
    </style>
    <body>
        <p>Haha</p >
        <p>Hehe</p >
    </body>
    
    2个p之间的距离只会相差 100px, 而不是200px
    补充: 如果margin不同,取最大一个值
    
    修改过后
    <style>
        .wrap {
            overflow: hidden;// 新的BFC
        }
        p {
            color: #f55;
            background: #fcc;
            width: 200px;
            line-height: 100px;
            text-align:center;
            margin: 100px;
        }
    </style>
    <body>
        <p>Haha</p >
        <div class="wrap">
            <p>Hehe</p >
        </div>
    </body>
    
    
    
    
    
    
    2. 浮动后,不计算内部高度
    <style>
        .par {
            border: 5px solid #fcc;
            width: 300px;
        }

        .child {
            border: 5px solid #f66;
            width:100px;
            height: 100px;
            float: left;
        }
    </style>
    <body>
        <div class="par">
            <div class="child"></div>
            <div class="child"></div>
        </div>
    </body>
    
    par的高度为0
    
    
    修改过后:
    <style>
        .par {
            border: 5px solid #fcc;
            width: 300px;
            overflow: hidden;
        }

        .child {
            border: 5px solid #f66;
            width:100px;
            height: 100px;
            float: left;
        }
    </style>
    <body>
        <div class="par">
            <div class="child"></div>
            <div class="child"></div>
        </div>
    </body>
        
css加载会造成阻塞嘛
css加载不会阻塞dom解析

css加载会造成dom渲染

css加载会阻塞js的执行

可以借鉴上面的: CSSOM树和DOM树是同时解析的吗?

请参考此文章

css 画一个三角形
   查看图一:
   .triangle {
       width: 0;
       height: 0;
       border: 40px solid transparent;
       border-top:60px solid balck;
   }
   
   border-top:60px 定义的是这个三角形的高

   
  在图一的基础上我们设置一条边border 为0的时候,会变成一个 直角三角形,请查看图二
  
   .triangle {
       width: 0;
       height: 0;
       border: 40px solid transparent;
       border-top:60px solid balck;
       border-left: 0;
   }
   

图一

image.png

图二

image.png

em/px/rem/vh/vw 有什么区别

相对单位:em, rem, vh/vw, % 等

绝对单位: px, pt 等

em:

em相对单位,基准点为父节点字体大小(继承父元素的字体大小),如果自身定义了 font-size按自身来计算(浏览器默认字号是 16px),整个页面内 1em不是一个固定的值。
如:比如浏览器的默认size是 16px, 父元素并没有设置字体大小,子元素的1em = 16px 如果父元素设置了字体大小为32px, 则子元素的1em = 32px

rem:

相对单位,可理解为‘root em’,相对根节点 html(网页)的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持。
不会像 em 那样,依赖于父元素的字体大小,从而造成混乱
如:浏览器的默认size是 16px,子元素都会相对于浏览器,不回去去找自身或者父元素的字体大小

vw:

v是 viewport,可视窗口的意思,w是 width,宽度的意思。
视窗宽度(视口宽度的 1%)
如:浏览器宽度1200px,1vw = 1200px/100 = 12px

css选择器有哪些,优先级分别是什么
css基本选择器
标签选择器:如body,div, p
id选择器: id='xxx'
类选择器: class='xxx'
后代选择器:(如:#head .nav ul li 从父集到子孙集的选择器)
子代选择器:(如:div>p ,带大于号>)
伪类选择器(如:就是链接样式,a元素的伪类,4种不同的状态:linkvisitedactivehover。)


优先级:

id选择器 > 类选择器 > 标签选择器 > 子代选择器 > 后代选择器 > 伪类选择器
说说你对盒模型的理解

当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)

一个盒模型由margin, border, padding, content 组成

IE盒模型:

盒子的宽度: width + margin

width: padding + content(content width) + border

标准盒模型:

盒子的宽度:padding + width + border + margin

width: width((content width))
如何使用css提高页面性能
1.内联首屏关键CSS
2.异步加载CSS
3.资源压缩
4.合理使用选择器
5.减少使用昂贵的属性
6.不要使用@import

请参考此文章

JS

js基础类型,引用类型
基础类型:Null, Undefined, Boolean, Number, String, BigInt,symbols(唯一且不可修改**的原始值)
引用类型:object(被看作是一组属性的集合)

栈:先进后出的数据结构,栈会自动分配内存空间,会自动释放,存放基本类型和引用数据类型的地址,
堆:动态分配的内存,大小不定也不会自动释放,存放引用类型的对象,指那些可能由多个值构成的对象,保存在堆内存中

eg 1.
    let a  = 1
    let b = a
    b  = 3
    console.log(a, b) // 1, 3
    
eg 2.
let a = 1
let b = 111

function fn(a,b){
  a = 10
  return a + b
}
fn(a,b)
console.log(a, b)
// 1, 111

在稍微改变一下
function fn1(b){
  a = 10
  return a + b
}

fn1(b)
console.log(a, b)
// 10, 111

有什么区别,为什么? 
因为在形参的时候,会进行一个浅拷贝
fn 的形参a进行了浅拷贝,形参a 和 全局a 都指向了1,
形参a = 10,进行了重新复制,然而全局的a 没有改变 


fn1里面的a 就是全局a, 所以在重新赋值的时候,全局a也改变了



eg3 .
    let obj = {
      a: 1,
      b: 2
    }
    let newObj = obj
    newObj.a = 10
    console.log(obj,newObj) //都是 { a: 10, b: 2 }

栈和堆

这篇文章值得推荐

总结一下这篇文章 :

  1. 全局变量:基础类型和引用类型的值都是存在堆的,但是地址是放在全局执行上下文的栈内存
  2. 被捕获的变量(闭包):基础类型和引用类型的值都是存在堆的,但是地址是放在调用变量的那个函数上面的栈
  3. 局部变量(未被捕获的):基础类型的值和地址都是放在栈的,引用类型的值是放在堆里面的,地址是放在 函数的栈的
js数组常用方法有哪些
1.map
2.forEach
3.filter
4.some
5.every
6.reduce
7.isArray
8.pop
9.push
10.splice
11.shift
12.join
13.reverse
14.unshift
15.concat
16.toString
17.indexOf
18.lastIndexOf
20.slice
21.sort

map

返回一个新的数组

const Arr = [1,2,3,4,5]

const newArray =  Arr.map(item => {
  return item + 1
})

console.log(Arr)
console.log(newArray)

// [ 1, 2, 3, 4, 5 ]
// [ 2, 3, 4, 5, 6 ]

forEach

第一个参数:数组中正在处理的元素
第二个参数:数组中正在处理的元素的index
第三个参数:当前遍历的数组
补充:不会被中断,不会返回新的数组

    eg。据列几个比较有意思的 demo
    // 深入研究的话,要去研究一下基础数据和引入数据的栈和堆
    1.
    const arr = [1,2,3,4]
    arr.forEach(item => {
        item*2
    })
    
    console.log(arr)
    // [ 1, 2, 3, 4 ]
    
    2.
    arr.forEach((item, index, arrry) => {
        arrry[index] = item*2
    })
    
    console.log(arr)
    // [ 2, 4, 6, 8 ]
    

reduce
第一个参数: callbakFn 第二个参数: 初始值

callbakFn: 第一个参数:如果有初始值则为初始值,否则为数组索引为 0 的元素 array[0]
第二个参数:数组中正在处理的元素
第三个参数:数组中正在处理的元素的index
第四个参数:当前遍历的数组

    eg.计算求和

    const arr = [1,2,3,4]
    const num = arr.reduce((pre,  cur) => {
      console.log(pre,  cur)
      return pre + cur
    }, 0)

    console.log(num) // 10
    
    
    eg.实现升降排序
    
    // ascending  升序
    // descending 降序
    let sort = 'ascending'

    const arr = [{ age: 9},{age: 1},{age: 8},{ age:2},{age: 7},{age: 3}]

    const sortFn = (arr, key, sort = 'ascending') => {
      let newArr = []
      let isSort = sort === 'ascending'
      arr.reduce((pre, curren) => {
        let index = pre.findIndex(item => isSort ? item[key] > curren[key] : item[key] < curren[key])
        return index === -1 ? pre.push(curren) &&  pre : pre.splice(index, 0, curren) && pre
      }, newArr)

      return newArr
    }

    console.log(sortFn(arr, 'age', sort))
    [
      { age: 1 },
      { age: 2 },
      { age: 3 },
      { age: 7 },
      { age: 8 },
      { age: 9 }
    ]
    

filter

会对数组中的元素挨个进行检过滤查,检查通过的,返回组成新的数组

  const arr = [1, 5, 10, 20]
  const newArr = arr.filter(item => item > 5)
  
  console.log(newArr)
  // [10,20]

some

返回一个Boolean, 测试数组中是不是至少有1个元素通过了检查,返回true

const arr = [1, 5, 10, 20]
 
console.log(arr.some(item => item > 10))

// true

every 返回一个Boolean, 测试数组中是不是全部元素通过了检查,返回true

const arr = [1, 5, 10, 20]
console.log(arr.every(item => item > 10))
// false

console.log(arr.every(item => item > 0))
// true
ES6+ 有哪些新特性
let、const 块级作用域
let 

   1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区

   2let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面

   3let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错

 const 

    1const声明一个只读的变量,声明后,值就不能改变

    2const并不是变量的值不能改动,而是变量指向的内存地址所保存的数据不得改动

    3let该有的特点const都有
解构
数组结构
const [a,b,...rest] = [1,2,3,4]
console.log(a, b, rest)
// 1 2 [ 3, 4 ]

对象结构
let { f1, f2 } = { f1: 'test1', f2: 'test2' } 
console.log(f1, f2) // test1, test2
模版字符串
   let a = 'l'
   let b = 'q'
   
   let c = `${a}${b}`
   // lq
class

class A {
  constructor(name, age, clothes) { 
      this.name = name; 
      this.age = age; 
      this.clothes = clothes;
  } 
  say() { 
      const sayStr = `Hello, my name is ${this.name}, ${this.age} years old!`; 
      console.log(sayStr); 
  }

  static wear() {
    return 'I wear cowboy'
  }
}


class B extends A {
  constructor(name, age, clothes) { 
    super(name, age, clothes);
    this.Bname = name; 
    this.Bage = age; 
    this.Bclothes = clothes;
  } 

  static wear() {
    return super.wear();
  }
}

console.log(B.wear())
// I wear cowboy

const aa = new B(1,2,3);

aa.say()
// Hello, my name is 1, 2 years old!
箭头函数
    () => console.log(1); 
    const a = () => 1; 
    const add = (a, b) => a + b; 
    const resFunc = (a, b) => () => a + b; // 返回一个函数
Promise

Promise 一步一步瓦解它,干掉它

一道面试题分享 :

async function async1(){
  console.log('async1 start')
  await async2()
  console.log('async1 end') 
}
async function async2() {
  console.log('async2')
  await fn()
}

const fn = () => {
  return new Promise((resolve, reject) => {
    console.log('fn start')
    resolve()
  }).then(() => {
    console.log('fn end')
  })
}
console.log('script start')

setTimeout(() => {
  console.log('setTimeout')
});

async1()//

//初始化Promise是,传入的函数会立刻被执行
new Promise((resolve=>{
  console.log('promise1')
  resolve()
})).then(function () {
  console.log('promise2')
})
console.log('script end')

// script start

// async1 start

// async2

// fn start

// promise1

// script end

// fn end

// async1 end

// promise2

// setTimeout
模块化
其它

await/async

new set/ new map

symbol/BigInt

扩展运算符

ES6模块化

如何区分数组和对象
1.
[] instanceof Array   // true
{} instanceof Array   // false


2.
Object.prototype.toString.call([])
'[object Array]'
Object.prototype.toString.call({})
'[object Object]'


3.
Array.isArray([])
// true
Array.isArray({})
// false

防抖和节流
防抖

在事件被触发的n秒后触发回调,如果在这n秒内有被触发,则重新计时

应用场景: 模糊查询, window触发resize 等

实现debounce

let timer = null
function debounce(fn, time) {
    let timer = null
    
    return function() {
        let _this = this
        let args = arguments
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          fn.apply(_this, args)
        }, time)
    }
}
节流

在规定时间内,只能触发一次,如果在这个单位时间内触发n次,只会触发一次

应用场景:监听滚动事件,比如是否滑到底部自动加载更多

实现

function throttle(fn, time) {
   let pretTimer = 0
   return function() {
       let _this = this
       let args = arguments
       let timer = new Date().getTime()
       if (timer - pretTimer > time) {
          fn.apply(_this, args)
          pretTimer = timer
       }
   }
}

promise方法有哪些,和怎么实现的

Promise 一步一步瓦解它,干掉它

this理解
1.默认绑定

在严格模式下,this指向的是undefined
在非严格模式下,this指向window


 eg.
 
    function foo () { console.log(this) } 
    foo() // => window 
    
    
    // 严格模式 
    function bar () { 
        'use strict' 
        console.log(this) 
    } 
    bar() 
    // => undefined

    
2.隐式绑定

由上下文对象调用,绑定到上下文对象
简单说,就是谁调用函数,就指向谁


function fn() {
    console.log(this)
}

let obj = {
    a: 1,
    fn: 
}
obj.fn() // obj 隐式绑定

let a = obj.fn
a() 
// window   思考?这个是隐式绑定还是默认绑定
其实2种都没得问题
(1window.a(), this指向了window   隐式绑定
(2)直接默认调用a函数。  this默认绑定了window.  默认绑定

在稍微的加一点难度

区分默认绑定和隐式绑定

let obj = {
    a: 1,
    fn: function() {
        console.log(this, '1')
        // obj 隐式绑定
        function fn1() {
            console.log(this, '2')
            // 这个this 打印的是window
            // fn1() 没有调用,默认是window.fn1()
            // 这个属于默认绑定
        }
        fn1()
    }
}

obj.fn()

隐式绑定 this 丢失的问题

  案列1:
  let obj = {
    a: 1,
    fn: function() {
            console.log(this)
        }
  }
  setTimeout(obj.fn, 0)
  // window
  ==>  setTimeout(function() {
            console.log(this)
        }, 0)
  
  解决方法:
  1. setTimeout(obj.fn(), 0) // 隐式绑定
  2. setTimeout(obj.fn.bind(obj), 0) // => obj // 显示绑定
  
  
  案列2:
   var name='zcxiaobao';
    function introduce(){
        console.log('Hello,My name is ', this.name);
    }
    const Tom = {
        name: 'TOM',
        introduce: function(){
            setTimeout(function(){
                console.log(this)
                console.log('Hello, My name is ',this.name);
            })
        }
    }
    const Mary = {
        name: 'Mary',
        introduce
    }
    const Lisa = {
        name: 'Lisa',
        introduce
    }

    Tom.introduce();
    setTimeout(Mary.introduce, 100);
    setTimeout(function(){
        Lisa.introduce();
    },200)
  
  // Tom.introduce(); Hello, My name is  zcxiaobao
  // setTimeout 回调函数的this指向window
  // setTimeout(Mary.introduce,100);   Hello, My name is  zcxiaobao
  //   setTimeout(function(){
  //      Lisa.introduce();
  //  },200)
  // Hello, My name is  Lisa    隐式绑定
3.显示绑定

直接将this绑定到指定对象

比如call, apply, bind

  • call()和apply()函数会立即执行
  • bind()函数会返回新函数,不会立即执行函数
  • call()和apply()的区别在于call接受若干个参数,apply接受数组。

function foo() {
    console.log(this.a);
};
var obj = {
    a: 2
};
foo.call(obj);
// 2
4.new绑定(构造函数)
请查看下面的new 的实现
箭头函数

箭头函数中的this,指向的是函数定义位置(定义作用域)的上下文this

如:

let obj = {
    a: 11,
    method: () => {console.log(this.a)}
}

obj.method()  // undefined
// 因为对象不是作用域

优先级

显式 > 隐式 > 默认

bind,call,apply 有什么区别,如何实现
区别

call: call 接受多个参数,第一个为上下文(this),后面的参数为函数本身的参数,立即执行 apply: apply接受2个参数,第一个是上下文(this),第二个参数是一个数组,立即执行 bind: bind 接受多个参数,第一个为上下文(this),后面的参数为函数本身的参数,返回一个函数(该函数使用apply的方法)

call 实现

    Function.prototype.call2 = function(context) {
      //判断当前context是否存在,不存在默认等于window
      context = context || window
      // 当前函数赋值给context的fn
      context['fn'] = this;
      // 获取除了第一个的其他参数
      let arg = [...arguments].slice(1)
      // 这里就是this隐式调用,执行
      context['fn'](arg)
      // 删除context 到fn
      delete context['fn']
    }

    function fn(arr) {
      console.log(this, arr)
    }


    let obj = {a: 1, b:2000}


    fn.call2(obj, 1, 2, 3) // 
    // {
    //   "a": 1,
    //   "b": 2000
    // }
    // [1, 2, 3]
apply 实现
    Function.prototype.apply2 = function(context, arr) {
      //判断当前context是否存在,不存在默认等于window
      context = context || window
      // 当前函数赋值给context的fn
      context['fn'] = this;

      if (arr) {
        context['fn'](arr)
      } else {
        context['fn']()
      }
      // 删除context 到fn
      delete context['fn']
    }
bind实现
Function.prototype.bind2 = function(context) {
  //判断当前context是否存在,不存在默认等于window
  context = context || window

  let self = this
  
  let arg = [...arguments].slice(1)

  return function() {
    let newArg = arg.concat([...arguments])
    self.apply2(context, newArg)
  }
}

function fn() {
  console.log(...arguments)
  console.log(this)
}


let obj = {a: 1, b:2000}


let newFn = fn.bind2(obj, 1,2,3) 

newFn(3,4)

// [
//   1,
//   2,
//   3,
//   3,
//   4
// ]

// {
//   "a": 1,
//   "b": 2000
// }
    
new的实现

初步查看new做了什么


    function school() {
        this.schoolName = 'LLL'
        this.schoolClothes = 'XXX'
    }
    
    school.prototype.schoolStyle = 'CCC'
    
    const children = new school()
    
    children.schoolName  // 'LLL'
    
    children.schoolStyle // 'CCC'

  1. 访问到了 school 构造函数的属性
  2. 访问到了 school 构造函数原型的属性

实现new

1.创建了一个新的对象 2.在arguments里面获取构造函数,并且在arguments里面去掉第一个构造函数 3.新的对象的原型指向构造函数的prototype 4.构造函数的this指向新的对象 5.判断第四步是否返回的是一个对象,如果不是则返回新建的对象

       function objectFactory() {
           let obj = {}
           constructor = [].shift.call(arguments)
           obj.__proto__ = constructor.prototype
           const ret = constructor.apply(obj, arguments)
           return typeOf ret === 'object' ? ret : obj
       }

js中原型,原型链理解
原型

每个javascript对象(除了null)在创建的时候,都会与之关联一个对象,而关联的哪个对象就是我们所说的原型,每个对象都会继承原型的属性

了解上面的话,首先得知道 实例, 构造函数, 实例原型 是什么?

通过一张图和一段代码来了解实例, 构造函数, 实例原型

68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f6d717971696e6766656e672f426c6f672f496d616765732f70726f746f74797065352e706e67.png

    function Person() {}
    
    Person.prototype.name = 'xl'
    
    const person = new Person()
    
    // person: 实例
    
    // Person: 构造函数
    
    // Person.prototype 实例原型
    
    
    比如:以上Person.prototype 就是 person的 原型
    
原型链

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线

补充:

   补充1:
   可以理解__proto__ === constructor.prototype
   
   通过以上的代码我们可以进行一个实验
   
    console.log(person.constructor === Person)  // true

    console.log(person.__proto__ === Person.prototype)  // true

    console.log(Person.prototype.constructor === Person)  // true
    
    
    
    补充2Object.prototype.__proto__  指向的是null
    
    
说说你对事件的循环的理解

Event Loop: 事件循环,是浏览器和node在执行javascript的时候不回阻塞的一种方式,这就是我们经常说的异步

    假设我们没有事件循环会造成什么
    
    const a = 1
    
    setTimeout(() => {console.log(a)}, 1000)
    
    console.log('2')
    
    // 等待一秒后执行 console.log(a),然后在执行console.log('2'),相当于中间的一秒什么都没有干
    
1. 栈 堆 队列

栈 堆: 请查看上面 ==> js基础类型,引用类型对他们的讲解

队列:又称队列元素,先进先出

2.MarockTask(宏任务)
`script`全部代码、`setTimeout``setInterval``setImmediate``I/O``UI Rendering`
3.MirockTask(微任务)
`Process.nextTick(Node独有)``Promise``MutationObserver`
总结

image.png

在 同步进行完成过后,执行栈为空,就回去检查微任务队列是否为空,如果不为空,就执行完所有的微任务,再去执行宏任务,为空就直接去执行宏任务
每单个宏任务执行完毕后,又去检查微任务队列是否为空,依次循环

作用域

分为静态作用域和动态作用域

js 是使用静态作用域

作用域 : 全局作用域,函数作用域,块级作用域(自行了解)

静态作用域:

    var a = '1'
    function fn() {
        console.log(a)
        // 1
    }
    
    function fn1() {
        var a = '2'
        console.log(a)  
        // 2
        fn()
    }
    
    fn1()
    
   
   此时有3个作用域: 全局, fn  fn1 ==> 同级,上一层作用域是全局
    
    
   在执行fn1的时候 a 会先找fn1作用域里面的a,所以打印是2
   继续执行fn,a 会先找fn作用域里面是否我a,没得就去上一层作用域全局,打印的结果是 1
    
闭包
 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。 -- JavaScript高级程序设计(第三版)
 
 我的理解: 一个函数调用另一个函数的内部变量

了解闭包首先要了解作用域(请参看上面的作用域),然后了解栈和堆

闭包的特点:
1. 函数嵌套函数
2. 内部函数调用外部函数的变量
3. 不会被垃圾机制回收

常用的地方:
1. 函数的柯里化
2. 防抖和节流

cookie, localstorage, sessionStorage
localStorage和sessionStorage

相同点:

  1. 储存大小都在5M 左右
  2. 都支持同源政策
  3. 都保存在客户端,不与服务端参加通信

不同点:
localStorage:
1. 永久保存,除非手动删除
2. 同一个浏览器里面,同源的情况下,可以共享所有数据

sessionStorage:
1. 页面关闭,就会自动清除缓存
2. 同一个浏览器里面,同源的情况下,新开的页面不回共享另一个页面的数据

cookie
  1. 保存数据小,限制在4kb 左右
  2. 每次都会存在http里面,如果cookie 很大,会导致性能问题
  3. 一般有服务器生成,可能设置消失时间
  4. 需要自己封装
数组扁平化
// 1.
let arr = [1,2,[3,4,[5,[6]]]] 
console.log(arr.flat(1))


// 2.
function fn(arr) {
    return arr.reduce(pre, cur => {
        pre.concat(Array.isArray(cur) ? fn(cur) : cur)
    }, [])
}