BFC

BFC(Block formatting context)直译为”块级格式化上下文”。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。

BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。

BFC布局规则:

  • Box垂直方向的距离由margin决定,同一个BFC内部会出现margin塌陷现象
  • BFC的区域不会与float box重叠
  • 浮动元素也参与计算BFC高度

创建BFC

  1. float的值不是none。
  2. position的值不是static或者relative。
  3. display的值是inline-block、table-cell、flex、table-caption或者inline-flex
  4. overflow的值不是visible

作用:解决margin塌陷、不会被浮动元素覆盖、解决浮动元素无法撑起父级元素

闭包

先来看一段代码

function f() {
  var book = 'JavaScript高级'
}
console.log(book);

这一段代码的执行结果当然是报错,因为book的作用域是在f函数内部,外部无法访问,函数执行时生成一个自己的执行上下文(执行环境),执行完毕时执行上下文销毁

作用域链

作用域链中保存着当前变量对象和所有父级变量对象

function f() {
  var book = 'JavaScript高级'
  return function () {
    console.log(book);
  }
}
var newF = f()
newF()

执行f函数,将返回值赋值给newF函数,这个返回值是一个匿名函数,这其实就是闭包,在这个匿名函数中保留着f的作用域,所以这里可以打印出book的值

经典面试题for循环延时打印

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  },1000)
}
// 5 5 5 5 5

结果为什么会是5个5呢,这里牵扯到了一点异步轮询,setTimeout属于异步方法,会在全局上下文执行完毕之后再之执行

执行过程为:for循环遍历5次,添加5个定时器,此时i=5,定时器时间到了之后,打印的是i的值,也就是5

那么怎么打印0-4呢?

  • 使用let声明i,let是块级变量
  • 使用闭包,闭包函数有自己的作用域可以在设置定时器时保留i的值

let就不说了,说一下闭包解决这个问题

for (var i = 0; i < 5; i++) {
  (function (j) {
    setTimeout(function () {
      console.log(j);
    }, 1000)
  })(i)
}
// 0 1 2 3 4

在么词设置定时器时,每次传进去的变量都不一样,每个定时器保留着着不同的作用域,所以结果是0 1 2 3 4

弊端

闭包虽好,但也有弊端——容易造成内存泄漏

根据js的垃圾回收机制,没有引用的变量将会自动回收,而如果使用闭包的形式通过函数表达式赋值给全局作用域中的一个变量,就可能会造成无法回收

原型

先来看几个概念

  • 原型——每一个函数都有一个prototype对象属性,指向另一个对象,prototype就是调用构造函数所创建的那个实例对象的原型
  • 原型链——每个对象都有一个__proto__属性,用于指向创建它的函数对象的原型对象,通过__proto__把对象属性连接起来就叫原型链

如果还有些许疑惑,那么看代码吧

function Person(name) {
  this.name = name
  this.sayHi = function() {
    console.log('Hi');
  }
}

Person.prototype.sayName = function () {
  console.log(this.name);
}

const p = new Person('张三')
p.sayHi()
p.sayName()
console.log(p.__proto__ === Person.prototype);

image-20201204141103365

第一段代码是一个构造函数,添加了一个属性和一个方法;第二段代码是往Person的原型上添加方法;第三段代码是创建了一个对象实例p,他的父类是Person,然后执行父类的方法,最后一行说明了p的__proto__就是Person的prototype,对象实例可以使用原型链上的的方法

继承

继承有很多种方法

  1. 原型链继承

    function Animals() {
      this.say = function () {
        console.log('Hi');
      }
    }
    function Cat() {
    
    }
    Cat.prototype = new Animals()
    

    通过把Animals的原型对象添加到Cat的原型对象上,实现继承

  2. 构造函数继承

    function Animals() {
      this.say = function () {
        console.log('Hi');
      }
    }
    function Cat() {
      Animals.call(this)
    }
    const c = new Cat()
    c.say()
    

    用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

  3. 组合继承

    function Animals(name) {
      this.name = name
      this.say = function () {
        console.log('Hi');
      }
    }
    function Cat() {
      Animals.call(this, 'king')
    }
    Cat.prototype = new Animals()
    const c = new Cat()
    c.say()
    

    将前面两种继承方式组合起来,解决了原型链继承不能传参和构造函数继承不能复用的问题,但是消耗内存,子类的构造函数会代替原型上的那个父类构造函数

  4. 原型式(实例)继承

    function Animals(name) {
      this.name = name
      this.say = function () {
        console.log('Hi');
      }
    }
    function content(obj) {
      function F() { }
      F.prototype = obj
      return new F()
    }
    var animal = new Animals()
    var cat = content(animal)
    

    通过对象实例来实现继承,但是无法复用

  5. 寄生组合继承

    function Animals(name) {
      this.name = name
      this.say = function () {
        console.log('Hi');
      }
    }
    
    function content(obj) {
      function F() { }
      F.prototype = obj
      return new F()
    }
    var proto = content(Animals.prototype)
    
    function Cat() {
      Animals.call(this)
    }
    Cat.prototype = proto   // 继承实例
    proto.constructor = Cat // 修复实例
    
    const c = new Cat()
    c.say()
    

    解决了组合继承的问题

插一嘴new的原理

var new1 = function (Class) {
  var newObj = Object.create(Class.prototype);
  var returnObj = Class.call(newObj);
  return typeof returnObj === 'object' ? returnObj : newObj
}
var s = new1(String)
console.log(s instanceof String); // true

首先创建一个新的对象,然后通过构造函数继承的方式,将要实例化对象的原型添加到创建的对象上

HTTP请求方法

GET

请求URL指定的资源

POST

传输实体主体

PUT

PUT方法用来传输文件,也可以实现与post相同的效果

POST和PUT的区别:PUT具有幂等性,也就是说多次PUT操作对服务器影响是一样的。举个例子,如果发布一篇文章,发送了两个请求,如果使用POST会产生两篇相同的帖子,使用PUT只会产生一个

DELETE

用来删除文件,是PUT的相反方法。

HEAD

获取报文首部,和GET方法一样,只是不返回报文主体部分

OPTIONS

查询针对请求URL指定的资源支持的方法

TRACE

让Web服务器端将之前的请求通信环回给客户端方法

CONNECT

要求在与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信。

this的指向

一句话来说就是:谁调用指向谁(个别情况除外)

改变this指向,可以看之前的一篇博客

  • 正常情况下 this 指向调用他的上下文
  • 箭头函数的 this 指向他的父作用域的 this(静态作用域、静态作用域、静态作用域)
  • new 会创建一个新的对象,this 指向这个对象,详情可以自行了解 new
  • call、bind、apply 会改变 this 的指向,详情自行了解

异步

异步轮询

Promise原理

元素垂直水平居中

  1. 父级元素display设置为table,子元素display设置为table-cell,子元素vertical-align: middle;
  2. 利用绝对定位,父级设置position为relative,子级设置position为absolute,距离顶部50%左侧50%,然后利用transform,translate(-50%, -50%);也可以使用margin-top和margin-left为负值
  3. 利用flex,将父级display设置为flex,然后justify-content: center; align-items: center;
  4. 利用父级伪类或子级空元素,display设置为inline-block,然后设置vertical-align: middle;

清除浮动

  1. 浮动元素后添加空标签,标签样式clear: both;
  2. 父级元素设置overflow: hidden;触发BFC,如果有定位谨慎使用
  3. after伪元素清除浮动,浮动元素父级添加伪元素,设置属性clear: both;

盒子模型

盒子模型包含margin(外边距)、padding(内边距)、border(边框)、content(内容)四部分

关于盒子模型存在两套标准

标准盒模型(W3C)

盒子宽度width=content

怪异盒模型(IE)

盒子宽度width=border+padding+content

这两个模型可以通过box-sizing来转换,conten-box是标准盒模型,border-box是怪异盒模型

数据类型

JS中有两大类数据类型,基本类型和引用类型

基本类型——Number、String、Boolean、Null、Undefined以及新加入的Symbol

引用类型——是一种数据结构,用于将数据和功能组织在一起,常见的有Object类型,Array类型,Date类型,RegExp类型(正则),Function类型

基本类型和引用类型的区别

基本类型是按值访问的,因为可以直接操作保存在变量中的实际值,保存在栈中;而引用类型大小是不固定的,存储在堆内存中,不可以直接访问和操作对内存空间,所以只能在栈中操作引用地址

例如

image-20201206134109844

基本类型在赋值时是将值重新添加到了b,此时改变a的值,b的值不受影响(传值)

而在引用类型中

image-20201206134616732

在赋值时,传递的是引用的地址,所以内容发生变化时所有的引用都会改变(传址)

栈(stack)会自动分配内存空间,会自动释放。堆(heap)动态分配的内存,大小不定也不会自动释放,需要程序员手动释放,如果不释放就会由OS在程序结束运行时释放。

此外注意,在函数传参时,引用类型传参也是传的地址,形参的操作会影响到原来的数据

let、const和var的区别

var存在变量提升,let和const没有

变量提升意味着可以先使用再声明,上代码

image-20201206190219810

而块级作用域会报错

image-20201206190516794

这就牵扯到了暂时性死区

暂时性死区:只要作用域内使用let声明了变量,变量就绑定了这个作用域,即使存在全局变量不会生效。使用let命令声明变量之前,该变量都是不可用的,这就叫暂时性死区。

let和const是块级变量

let和const都只会在声明所在的作用域块中生效,let和const的区别是,const声明时必须赋值,而且一旦赋值不可改变

image-20201206191340358

同一作用域下const和let不能重复声明

image-20201206192929928

事件委托

事件委托主要利用了冒泡原理实现,从最深的结点开始,向外层传递事件,我们可以只给父级添加事件,然后通过target来触发事件

例如在ul>li中,如果添加事件一般是在每个li上添加事件,如

<ul id="ul">
  <li id="li1">第一个</li>
  <li id="li2">第二个</li>
  <li id="li2">第三个</li>
  <li id="li2">第四个</li>
</ul>
<script>
  var liNodes = document.querySelectorAll('li')
  for(let i =0; i < liNodes.length; i++) {
    liNodes[i].onclick = function() {
      console.log(liNodes[i].innerText);
    }
  }
</script>

![QQ录屏20201207140026 00_00_00-00_00_30](https://cdn.easyremember.cn/img/QQ录屏20201207140026 00_00_00-00_00_30.gif)

这样再每个节点上都加上监听会影响性能,这时候就可以利用事件委托在这些元素的父级元素上添加监听事件

事件委托
<ul id="ul">
  <li id="li1">第一个</li>
  <li id="li2">第二个</li>
  <li id="li2">第三个</li>
  <li id="li2">第四个</li>
</ul>
<script>
  var ulNode = document.getElementById('ul')
  ulNode.onclick = function(e) {
    console.log(e.target.innerText);
  }
</script>

![QQ录屏20201207140507 00_00_00-00_00_30](https://cdn.easyremember.cn/img/QQ录屏20201207140507 00_00_00-00_00_30.gif)

就可以实现相同的效果

ES6和CommonJS模块化

区别:

  • CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用
  • CommonJS模块是运行时加载,ES6模块是编译时输出接口

ES6

使用export关键字导出变量或方法,使用import关键字导入模块;

此外还有默认导出export default,只能默认导出一次;

export导出的内容会被放到一个对象中,导入时使用{}来解构取值,export default导出的是什么导入就是什么

CommonJS

使用module.exports关键字导出模块,使用require关键字倒入模块

可以使用解构或者.属性名来按需引入如

// 导出
module.exports =  {
  obj: {
    name: '张三',
    age: 14,
    out() {
      console.log(this.age);
    }
  }
}
// 导入
const obj = require('./1').obj
// 或者
const {obj} = require('./1')

混用

  • module.exports可以看做一个export default,可以直接import
  • 使用require导入export default时需要写成require().default

从输入url到显示内容发生了什么

输入网址

在浏览器地址栏输入url

查找域名IP地址

首先从本地hosts文件中寻找对应的ip,如果由直接使用,没有的话发出DNS请求到本地DNS服务器,本地DNS先查找缓存,如果有返回目标ip,如果还没有找到,本地DNS就会到DNS根服务器查找,如果根DNS没有记录域名和IP的对应关系就会告诉本地DNS去域服务器查找,然后本地DNS再请求域服务器,域服务器再告诉本地DNS对应的解析服务器地址,最后本地DNS向解析服务器发送请求,然后获取到域名对应的IP,并且缓存下来

建立TCP连接

获取到对应的IP之后,向目标服务器发起TCP连接请求,经过三次握手之后建立连接

发送HTTP请求

建立好TCP连接之后,发起HTTP请求

服务器处理

服务器接收到浏览器发来的HTTP请求之后,处理相应的事务,将所需要的资源返回给浏览器

关闭TCP连接

为了避免资源占用,在资源传输完成之后将TCP连接断开,经过四次挥手之后连接中断

浏览器解析资源渲染

解析过程如下(来自网络,侵权联系删除)

image-20201210220303700

怎么判断一个变量是数组

  • 基于instanceof
  • 基于constructor
  • 基于Object.prototype.isPrototypeOf
  • 基于getPrototypeOf
  • 基于Object.prototype.toString
  • 基于Array.isArray
var a = []
a instanceof Array;
a.constructor === Array;
Array.prototype.isPrototypeOf(a);
Object.getPrototypeOf(a) === Array.prototype;
Object.prototype.toString.apply(a) === '[object Array]';
Array.isArray(a)

前端小白