面试题

[TOC]

HTML

1. html5有什么优化

  • 语义化标签的出现
    • header
    • footer
    • section 等等等等
  • 还有 input 的优化
    • 可以给它的 type 设置成 number、tel、email 等等
  • 增加了表单的优化
    • placeholder、required、min/max、multiple 等等
  • 增加了 canvas
  • 增加了 audio播放音频文件的标签

2. 如何理解HTML语义化

概念:合理正确的使用语义化的标签来创建页面结构(正确的标签做正确的事)

为了避免大篇幅的没有语义的标签来构建页面。

标签:header nav main article section aside footer

优点:1. 在没CSS样式的情况下,页面整体也会呈现很好的结构效果

        2. `代码结构清晰`,易于阅读,
        2. `利于开发和维护` 方便其他设备解析(如屏幕阅读器)根据语义渲染网页。
        2. `有利于搜索引擎优化(SEO)`,搜索引擎爬虫会根据不同的标签来赋予不同的权重

3. meta标签

为浏览器提供 html 的元信息

规定 html 字符编码

1
<meta charset="UTF-8">

设置移动端的视区窗口

1
<meta id="viewport" name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1; user-scalable=no;">

设置 http 头

1
<meta http-equiv="content-Type" content="text/html; charset=gb2312">

图片403

1
<meta name="referrer" content="no-referrer" />

4. src 和 href 的区别

  1. 请求资源不同。href:超文本引用,用于建立文档与资源的联系,常用的有:link,a。src:将其所指向的资源下载并应用。
  2. 作用结果不同:href用于文档与资源之间建立联系。src请求到的资源替换当前内容。
  3. 浏览器的解析不同:href将资源解析成css文件,并行加载请求资源,不会阻塞对当前文档的处理。src会暂停其他资源的处理,直到该资源加载、解析和执行完毕。

CSS

0. css3有什么优化

  • Border-radius、Border-shadow、border-image
  • 还有动画效果 translate3d,css3的 translate3d 可以开启 gpu 加速,所以在使用动画的时候尽量要 css3的 translate3d,而不是 position:absolute+top+left
  • box-sizing

1. CSS选择器及优先级

带!important 标记的样式属性优先级最高; 样式表的来源相同时:!important > 行内样式>嵌入样式和外链样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性

2. position 属性的值有哪些及其区别

固定定位 fixed: 元素的位置相对于浏览器窗口是固定位置,即使窗口是滚动的它也不会移动。Fixed 定 位使元素的位置与文档流无关,因此不占据空间。 Fixed 定位的元素和其他元素重叠。
相对定位 relative: 如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直 或水平位置,让这个元素“相对于”它的起点进行移动。 在使用相对定位时,无论是 否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框。
绝对定位 absolute: 绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那 么它的位置相对于浏览器窗口。absolute 定位使元素的位置与文档流无关,因此不占据空间。 absolute 定位的元素和其他元素重叠。
粘性定位 sticky: 元素先按照普通文档流定位,然后相对于该元素在流中的 flow root(BFC)和 containing block(最近的块级祖先元素)定位。而后,元素定位表现为在跨越特定阈值前为相对定 位,之后为固定定位。
默认定位 Static: 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声 明)。 inherit: 规定应该从父元素继承 position 属性的值。

3. box-sizing属性

box-sizing 规定两个并排的带边框的框,语法为 box-sizing:content-box/border-box/inherit
content-box:宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边距和边框。【标准盒子模型】
border-box:为元素设定的宽度和高度决定了元素的边框盒。【IE 盒子模型】
inherit:继承父元素的 box-sizing 值。

4. CSS 盒子模型

CSS 盒模型本质上是一个盒子,它包括:边距,边框,填充和实际内容。CSS 中的盒子模型包括 IE 盒子模型和标准的 W3C 盒子模型。
在标准的盒子模型中,width 指 content 部分的宽度
在 IE 盒子模型中,width 表示 content+padding+border 这三个部分的宽度

5. BFC(块级格式上下文)和IFC(行级格式化上下)

BFCBlock Formatting Context 的缩写,即块级格式化上下文。BFC是一个独立的渲染区域,规定了内部box如何布局, 并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部 box 垂直放置,计算 BFC 的高度的时候,浮动元素也参与计算

创建:

  • 绝对定位元素(positionabsolutefixed )。
  • 行内块元素,即 displayinline-block
  • overflow 的值不为 visible

应用: 1.分属于不同的BFC时,可以防止margin重叠 2.清除内部浮动 3.自适应多栏布局

IFC:内联格式上下文

布局规则:1.内部的盒子会在水平方向,一个个地放置;
2.IFC的高度,由里面最高盒子的高度决定;
3.当一行不够放置的时候会自动切换到下一行

6. 水平垂直居中

  1. 利用transform,设置 left: 50%top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 translate 来调整子元素的中心点到父元素的中心。该方法可以不定宽高

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      .father {
    position: relative;
    }
    .son {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    }

  2. 利用绝对定位,设置 left: 50%top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-leftmargin-top 以子元素自己的一半宽高进行负值赋值。该方法必须定宽高

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    .father {
    position: relative;
    }
    .son {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 200px;
    height: 200px;
    margin-left: -100px;
    margin-top: -100px;
    }

  3. 利用 flex

    1
    2
    3
    4
    5
    .father {
    display: flex;
    justify-content: center;
    align-items: center;
    }

自适应两栏布局

通过BFC和浮动

1
2
3
4
5
6
7
8
9
10
11
.content{
width: 100%
}
.left{
width: 200px;
margin-right: 10px;
float: left;
}
.right{
overflow: hidden;
}
1
2
3
4
5
6
7
.left{
width: 200px;
float: left;
}
.right{
margin-left: 210px;
}

通过定位

1
2
3
4
5
6
7
8
9
10
11
12
.content{
position: relative;
width: 100%;
}
.left{
width: 200px;
position: absolute;
}
.right{
position: absolute;
left: 210px;
}

通过flex:

1
2
3
4
5
6
7
8
9
10
11
.content{
width: 100%;
display:flex;
}
.left{
width: 200px;
margin-right:10px;
}
.right{
flex:1;
}

三栏布局

普通三栏:通过定位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.box {
position: relative;
}
.left {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
}
.right {
position: absolute;
top: 0;
right: 0;
width: 200px;
height: 200px;
}
.middle {
margin-left: 210px;
margin-right: 210px;
height: 200px;
}

通过浮动实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.box {
overflow: hidden;
}
.left {
float: left;
background-color: gray;
width: 200px;
height: 200px;
}
.right {
float: right;
background-color: gray;
width: 200px;
height: 200px;
}
.middle {
height: 200px;
background-color: lightgray;
margin-left: 210px;
margin-right: 210px;
}

圣杯布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.container {
padding-left: 220px;//为左右栏腾出空间
padding-right: 220px;
}
.left {
float: left;
width: 200px;
height: 400px;
background: red;
margin-left: -100%;
position: relative;
left: -220px;
}
.center {
float: left;
width: 100%;
height: 500px;
background: yellow;
}
.right {
float: left;
width: 200px;
height: 400px;
background: blue;
margin-left: -200px;
position: relative;
right: -220px;
}

7. 用CSS实现三角符号

1
2
3
4
5
6
7
8
div { 
width: 0px;
height: 0px;
border-right: 10px solid transparent;
border-top: 30px solid #ff0;
border-left: 10px solid transparent;
}
/*记忆口诀:盒子宽高均为零,三面边框皆透明。 */

8. 常见的布局类型(查)

浮动,定位,flex,grid网格布局

浮动:

  • 优点:兼容性好
  • 缺点:浮动会脱离标准文档流

定位:

  • 优点:快捷
  • 缺点:子元素也脱离了标准文档流,可用性差

flex布局:弹性布局

网格布局:

9. 浮动(查)

设置浮动的图片,文字会环绕图片,设置浮动的块级元素,可以排列在一行,设置浮动的行内元素,可以设置宽高。

设置了浮动的元素会脱离文档流,如果父级盒子没有设置宽高,需要子盒子将她撑起来,则会造成高度塌陷。也会影响其他元素的排列。

  1. 额外标签法(在最后一个浮动标签后,新加一个标签,给其设置clear:both)

    新添加了一个标签,会造成不必要的渲染

  2. 父级添加overflow属性(overflow: hidden;)

    写法方便简单,但是如果盒子中有定位元素超出了父级,则超出部分会隐藏。

  3. 使用after不会新增加标签,不回影响元素,是最流行的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
.clearfix::after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: table;
clear:both;
}
.clearfix{
*zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
}
</style>
<body>
<div class="fahter clearfix">
<div class="big">big</div>
<div class="small">small</div>
<!--<div class="clear">额外标签法</div>-->
</div>

10. 常见的行内元素和块级元素

行内元素 inline

  • span、input、img、textarea、label、select
  • 不能设置宽高,多个元素共享一行,占满的时候会换行

块级元素block

  • p、h1/h2/h3/h4/h5、div、ul、li、table
  • 可以设置宽高,一个元素占满一整行

inline-block

  • 可以设置宽高,多个元素共享一行,占满的时候会换行

display

11. px,em,rem,vw,vh,rpx等单位的特性

px:像素

em:当前元素的字体大小

rem:根元素字体大小

vw:相对于视窗的宽度,视窗宽度是100vw

vh:相对于视窗的高度,视窗高度是100vh

rem单位翻译为像素值的时候是由html元素的字体大小决定的。此字体大小会被浏览器中字体大小的设置影响,除非显式的在html为font-size重写一个单位。

2.em单位转换为像素值的时候,取决于使用它们的元素的font-size的大小,但是有因为有继承关系,所以比较复杂。

12. 什么是DOM事件流?什么是事件委托(查)

  • DOM事件流:事件发生时会在元素节点之间按照特定的顺序进行传播
    • ​ 捕获阶段:最顶层节点开始,向下传播到目标元素接受的过程
    • ​ 目标阶段
    • ​ 冒泡阶段:目标元素接受然后逐级的向上传播
  • 在addeventListener()的第三个参数(useCapture)设为true,就会在捕获阶段运行,默认是false冒泡
  • 事件委托:
    • 利用冒泡原理(子向父一层层穿透),把事件绑定到父元素中,以实现事件委托

13. 事件冒泡和事件捕捉有什么区别(查)

  • 事件冒泡
    • addEventListener中的第三属性设置为false(默认)
    • 从下至上(儿子至祖宗)执行
    • 阻止事件冒泡:
      • e.stopPropagation
  • 事件捕捉
    • addEventListener中的第三属性设置为true
    • 从上至下(祖宗到儿子)执行

14. link和@import

1、从属关系:link是html的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等;而@import是css的语法,只有导入样式表的作用。

2、加载顺序:页面被加载时,link会和html同时被加载而;@import引入的 CSS 将在页面加载完毕后被加载。

3、兼容性:@import是 CSS2.1 才有的语法,所以只能在 IE5以上 才能识别;而link是 HTML 标签,所以不存在兼容性问题。

4、DOM:javascript只能控制dom去改变link标签引入的样式,而@import的样式不是dom可以控制的。

5、link方式的样式权重高于@import的权重

15. 响应式布局

响应式布局指的是同一页面在不同屏幕尺寸下有不同的布局。。

  1. 媒体查询:可以让我们针对不同的媒体类型定义不同的样式,当重置浏览器窗口大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。
  2. rem布局:默认情况下我们html标签的font-size为16px,我们利用媒体查询,设置在不同设备下的字体大小。
  3. 如果通过rem来实现响应式的布局,只需要根据视图容器的大小,动态的改变font-size即可。

16. 元素不可见

display:none

  • 该元素和他的子元素不会被渲染
  • 会让元素从DOM消失,渲染的时候不占据任何空间

visibility:hidden

  • 隐藏元素而不更改文档的布局
  • 不会让元素从DOM消失,渲染的时候任然占据原来的空间

opacity:0

  • 元素完全透明
  • 不会让元素从DOM消失,渲染的时候任然占据原来的空间

17. css动画属性

  • transform:translate(参数1,参数2)———位移属性

  • transform:scale()———-2D缩放

  • transform:rotate()———2D旋转

  • @keyframes 关键帧名称{
    from{初始状态属性}
    to{结束状态属性}}

animation属性

  • animation-name

所应用的动画名称(关键帧名称),必须配合@keyframes使用。

  • animation-duration

动画持续的时间,例:animation-duration:3s; 即为动画的持续时间为3s。

  • animation-timing-function

动画的运动状态:
linear:匀速。等同于贝塞尔曲线(0.0, 0.0, 1.0, 1.0)
ease:平滑。等同于贝塞尔曲线(0.25, 0.1, 0.25, 1.0)
ease-in:由慢到快。等同于贝塞尔曲线(0.42, 0, 1.0, 1.0)
ease-out:由快到慢。等同于贝塞尔曲线(0, 0, 0.58, 1.0)
ease-in-out:由慢到快再到慢。等同于贝塞尔曲线(0.42, 0, 0.58, 1.0)

  • animation-iteration-count

动画的循坏次数:
infinite: 无限循环
number: 循环的次数

JS

0. Object常用的API

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

1
2
3
4
5
6
7
8
const person = {
isHuman: false,
printIntroduction: function() {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};

const me = Object.create(person);

Object.is()方法判断两个值是否为同一个值

Object.hasOwnProperty()方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)

**Object.assign()** 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

**Object.values()**方法返回一个给定对象自身的所有可枚举value的数组

1. 基本数据类型

UndefinedNullBooleanNumberStringSymbolBigInt

  • Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。
  • BigInt 可以表示任意大小的整数。处理转换当中的精度缺失,它提供了一种方法来表示大于 2^53 - 1 的整数,在最后加n定义

2. 数据类型检测

不同类型 typeof instanceof Object.prototype.toString.call()
优点 快速区分基本类型 能够区分Array、Object和Function,原理:查找目标对象的原型链 精准判断数据类型
缺点 不能将Object、Array和Null区分,都返回object Number,Boolean,String基本数据类型不能判断 写法繁琐不容易记,推荐进行封装后使用

constructor:返回创建实例对象的构造函数的引用

如何判断变量是否为数组?

1
2
3
4
5
Array.isArray(arr); // true
arr.__proto__ === Array.prototype; // true
arr instanceof Array; // true
Object.prototype.toString.call(arr); // "[object Array]"

判断一个数是整数:

Number.isInteger,取余

类数组转换为数组

1
Array.prototype.slice.call(arguments);

array = Array.from(arr);

数据类型转换:

强制转换

  • Number()
  • parseInt()
  • String()和toString()
  • Boolean()

自动转换

  • 比较运算
  • 算术运算

4. 作用域和作用域链

作用域:规定变量和函数的可适用范围称作用域,分为全局作用域和局部作用域

  • 全局作用域为程序的最外层作用域,一直存在
  • 函数作用域只有函数被定义时才会创建,包含在父级函数作用域 / 全局作用域内。
  • 由于作用域的限制,每段独立的执行代码块只能访问自己作用域和外层作用域中的变量,无法访问到内层作用域的变量。

作用域链:当可执行代码内部访问变量时,会先查找本地作用域,如果找到目标变量即返回,否则会去父级作用域继续查找…一直找到全局作用域。我们把这种作用域的嵌套机制,称为作用域链。

5. 什么是闭包

闭包是指嵌套在一个函数内部中的函数。当一个嵌套的子函数引用了外部复函数的变量时,就产生了闭包。

原理:当前作用域可以访问上级作用域中的变量

作用:

(1)保护:
(2)保存:当前上下文的某些内容被上下文以外的内容占用,当前上下文不被释放,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来

优点和缺点:延长局部变量的生命周期,但会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

应用:setTimeout,柯里化(是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。),模仿块级作用域,函数防抖和节流

1
2
3
4
5
6
7
8
9
10
11
12
function create() {
const a = 100;

return function () {
console.log(a);
};
}

const fn = create();
const a = 200;
fn(); // 100

解决办法:在退出函数之前,将不使用的局部变量全部删除。

6. this的指向

  • this 总是(非严格模式下)指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境;
  • this 在严格模式下 指向 undefined
  • this存在的环境有三种,全局执行上下文和函数执行上下文,eval。

1、作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象

1
2
3
4
5
6
7
8
9
var obj = {
a: 'yuguang',
getName: function(){
console.log(this === obj);
console.log(this.a);
}
};

obj.getName(); // true yuguang

2、作为普通函数调用

this总是指向全局对象(在浏览器中,通常是Window对象)

1
2
3
4
5
6
7
8
9
10
11
window.name = '老王'
var obj = {
name: 'yuguang',
getName: function(){
console.log(this.name);
}
};

var getNew = obj.getName;
getNew(); // 老王

3、构造器调用

当new运算符调用函数时,总是返回一个对象,this通常也指向这个对象

1
2
3
4
5
var MyClass = function(){
this.name = 'yuguang';
}
var obj = new MyClass();
obj.name; // yuguang

但是,如果显式的返回了一个object对象,那么此次运算结果最终会返回这个对象

1
2
3
4
5
6
7
8
9
var MyClass = function () {
this.name = 1;
return {
name: 2
}
}
var myClass = new MyClass();
console.log('myClass:', myClass); // { name: 2}

4、call或apply调用

跟普通的函数调用相比,用call和apply可以动态的改变函数的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj1 = {
name: 1,
getName: function (num = '') {
return this.name + num;
}
};

var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4

5.箭头函数

箭头函数不会创建自己的this,它只会从自己的父类执行上下文继承this。

1
2
3
4
5
6
7
8
9
10
this.val = 2;
var obj = {
val: 1,
getVal: () => {
console.log(this.val);
}
}

obj.getVal(); // 2

7. new 实现

  1. 首先创一个新的空对象。

  2. 根据原型链,设置空对象的 __proto__ 为构造函数的 prototype

  3. 构造函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)。

  4. 判断函数的返回值类型,如果是引用类型,就返回这个引用类型的对象。

  5. function createPerson(name,age){
        // 1.实例化Object对象
        const o=new Object()
        // 2.改变构造函数的this指向
        Person.call(o,name,age)
        // 3.原型链继承
        o.__proto__=Person.prototype
        // 4.返回这个对象
        return o
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
       
    创建一个空对象

    `object.create(null)`

    ### 8. **0.1+0.2>0.3**

    因为在JS底层中,每个变量是以二进制表示,固定长度为64位,其中第1位是符号位,再往后11位是指数为,最后52表示的是尾数位,而0.1和0.2转为二进制的时候是无限循环小数,所以JS就会进行截取,截取以后0.1和0.2就不是他们本身了,要比原来大那么一丢丢,所以0.1+0.2就>0.3了

    解决:先给他们**放大倍数**,随后在**除以相应倍数**

    ```javascript
    const a = 0.1;
    const b = 0.2;

    console.log(a + b === 0.3) // false
    console.log((a * 1000 + b * 1000) / 1000 === 0.3) // true

9. == 和===的区别

===是严格意义上的相等,会比较两边的数据类型和值大小

==是非严格意义上的相等

Null == Undefined ->true
String == Number ->先将String转为Number,在比较大小
Boolean == Number ->现将Boolean转为Number,在进行比较
Object == String,Number,Symbol -> Object 转化为原始类型

10. NaN === NaN返回什么

返回 falseNaN永远不等于NaN,判断是否为NaN用一个函数 isNaN来判断;

isNaN传入的如果是其他数据类型,那么现将它使用Number()转为数字类型在进行判断

11. 手写call、apply、bind(查)

三者都是改变this的指向的方法,

判断是否是函数,call和apply都会调用函数,改变函数内部this的指向。

call和apply传递的参数不一样

call:

  • 如果不传参数,或者第一个参数是nullnudefinedthis都指向window
  • 第一个参数是谁,this就指向谁,包括null和undefined,如果不传参数this就是undefined

apply:

  • apply把需要传递给fn的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn一个个的传递

bind:改变函数内部的this指向,函数不会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.myCall = function (context) {
// 先判断调用myCall是不是一个函数
// 这里的this就是调用myCall的
if (typeof this !== 'function') {
throw new TypeError("Not a Function")
}

// 不传参数默认为window
context = context || window

// 保存this
context.fn = this

// 保存参数
let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组

// 调用函数
let result = context.fn(...args)

delete context.fn

return result

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Function.prototype.myApply = function (context) {
// 判断this是不是函数
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}

let result

// 默认是window
context = context || window

// 保存this
context.fn = this

// 是否传参
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn

return result
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Function.prototype.myBind = function(context){
// 判断是否是一个函数
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用bind的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments,1)
// 返回一个函数
return function F () {
// 判断是不是new出来的
if(this instanceof F) {
// 如果是new出来的
// 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
return new _this(...args,...arguments)
}else{
// 如果不是new出来的改变this指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}

12. 执行栈和执行上下文

执行栈:

  • 首先栈特点:先进后出
  • 每次函数调用时,就会创建出它的执行上下文,然后进行压栈,当函数执行完成时,它的执行上下文就会被销毁,进行弹栈。
  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
  • 只有浏览器关闭的时候全局执行上下文才会弹出

执行上下文:

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
  • 函数执行上下文:每次函数调用时,都会新创建一个函数执行上下文

13. 原型,原型链

每个实例对象上都有一个 __proto__ 隐式原型属性,每个构造函数都有一个prototype显示原型属性,两者都指向了构造函数的原型对象上。原型对象上的方法和属性能够被实例对象所共享。而原型对象上也是一个对象,他也有自己的原型。就这样一层一层的往上,直到Object的原型对象上的__proto__为null,就这样一层一层的链条形成了原型链。这也就是为什么每个对象都能够使用Object.prototype.tostring的方法

14. JS 中的常用的继承方式

原型继承、构造函数继承、组合继承,寄生组合继承、ES6的extend

原型继承:会共享引用属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   // ----------------------方法一:原型继承
// 原型继承
// 把父类的实例作为子类的原型
// 缺点:子类的实例共享了父类构造函数的引用属性

//父类
function Person(name,age){
this.name = name || 'unknow'
this.age = age || 0
}

//子类
function Student(name){
this.name = name
this.score = 80
}

//继承
Student.prototype = new Person();

var stu = new Student('lucy');
console.log(stu.name); //lucy --子类覆盖父类的属性
console.log(stu.age); // 0 --父类的属性
console.log(stu.score); // 80 --子类自己的属性

构造函数继承:会独享所有属性,包括引用属性(重点是函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	//父类
function Person(){
this.hobbies = ['music','reading']
}

//子类
function Student(){
Person.call(this);
}

var stu1 = new Student();
var stu2 = new Student();

stu1.hobbies.push('basketball');

console.log(stu1.hobbies); // ["music", "reading", "basketball"]
console.log(stu2.hobbies); // ["music", "reading"]

组合继承:利用原型链继承要共享的属性,利用构造函数继承要独享的属性,实现相对完美的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ----------------------方法二:组合继承
// 在子函数中运行父函数,但是要利用call把this改变一下,
// 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor

// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
// 优点可传参,不共享父类引用属性
function Father(name) {
this.name = name
this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
console.log(this.name);
}

function Son(name, age) {
Father.call(this, name)
this.age = age
}

Son.prototype = new Father()
Son.prototype.constructor = Son


var s = new Son("ming", 20)

console.log(s);

寄生组合继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ----------------------方法三:寄生组合继承
function Father(name) {
this.name = name
this.hobby = ["篮球", "足球", "乒乓球"]
}

Father.prototype.getName = function () {
console.log(this.name);
}

function Son(name, age) {
Father.call(this, name)
this.age = age
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

var s2 = new Son("ming", 18)
console.log(s2);

extend:

1
2
3
4
5
6
7
8
9
10
11
// ----------------------方法四:ES6的extend(寄生组合继承的语法糖)
// 子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话
// 必须是 super 。

class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
constructor(y) {
super(200) // super(200) => Father.call(this,200)
this.y = y
}
}

15. 内存泄漏

概念:内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏(全局变量、闭包、DOM 元素的引用、定时器)

原因:内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法使用,积少成多,系统会越来越卡以至于崩溃

解决:避免使用全局变量,手动删除定时器和DOM,removeEventListener移除事件监听。

16. 垃圾回收机制:

  • 标记清除法

垃圾回收机制获取根节点并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除

  • 引用计数法

当声明一个变量并赋值时,值+1,当该值被取代时-1,为0时进行删除。

17. 深拷贝和浅拷贝

浅拷贝:创建了一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,则拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

  1. Object.assign() 方法: 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
  2. Array.prototype.slice():slice() 方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
  3. 拓展运算符...

深拷贝:拷贝所有的属性,并在计算机中开辟出了一块新的内存,拷贝前后两个对象互不影响。

js实用工具库,提供了许多API

  • Lodash中的_.cloneDeep(),_.merge(a,b)(合并),_.reject()(根据条件去除某个元素)
  • 手写循环递归
1
2
3
4
5
6
7
8
9
10
11
module.exports = function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
  • JSON.parse(JSON.stringify())

缺点:NaN ===> null

​ undefined 函数 ===> 丢失

​ 时间戳 ===> 字符串时间

​ 错误信息 ===> 空对象

​ 循环引用对象 ===》 报错

​ obj中有属性是new出来的,则会丢弃对应的constructor 初始化构造器

18. js是单线程

因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器不知道该听谁的。

19. promise

promise是异步编程的解决方案,简单来说就是一个容器,里面保存着一个异步操作的结果,从语法上说,promise是一个对象,可以改变对象获取异步操作的消息。promise共有三个状态

pending(执行中)、success(成功)、rejected(失败)

状态只能由 Pending –> Fulfilled 或者 Pending –> Rejected,且一但发生改变便不可二次修改

作用:他可以解决回调地狱的问题,也就是异步深层嵌套的问题。

基本使用:我们可以new一个promise promise的构造函数接受一个函数,并且传入两个参数:resolvereject分别表示异步操作执行成功的回调和失败的回调。promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数。

.catch():获取异常信息

.all():Promise.all()创建的Promise会在这一组Promise全部解决后在解决。也就是说会等待所有的promise程序都返回结果之后执行后续的程序。返回一个新的Promise。应用:当需要加载的资源较多时。

  • 如果所有都成功,则合成Promise的返回值就是所有子Promise的返回值数组。
  • 如果有一个失败,那么第一个失败的会把自己的理由作为合成Promise的失败理由

.race():得到的却是数组中跑的最快的那个,当最快的一跑完就立马结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var flag = false
if(flag) {
resolve('hello')
} else {
reject('出错了')
}
}, 2000);
});
p.then(function(data) {
console.log('成功回调',data)
},function(info) {
console.log('失败了',info))

通过promise获取网络图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<title>手写promise加载图片</title>
</head>
<body>
<script type="text/javascript">
const url1='https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3719520752,2657134858&fm=26&gp=0.jpg'//网上随便找一个图片的地址
const url2='https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3461776228,1302027451&fm=26&gp=0.jpg'
function loadImage(src){//定义一个异步加载图片的函数
const p = new Promise((resolve,reject)=>{//new一个promise对象
const img =document.createElement('img')//创建一个img节点
img.src=src//把我们的图片地址给我们新建的节点
img.onload=()=>{//通过监听节点成功建立(当然图片也传进去)调用resolve
resolve(img)//返回整个节点,可以用.then捕捉
}
img.onerror=()=>{//监听错误,可以用.catch捕捉
reject(`图片加载失败,地址为${src}`)
}


})
return p//记得把整个promise对象返回
}
loadImage(url1)//调用异步函数
</script>
</body>
</html>

20. async和await

作用:async用于申明一个function是异步的,而await用于等待一个异步方法执行完成,他可以很好的替代promise中的then。

async函数返回一个promise对象,当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,在接着执行函数体内后面的语句

21. 宏任务和微任务

  • 宏任务:script,setTimeOutsetInterval,DOM事件
  • 微任务:promise.then,process.nextTickMutationObserver
  • 微任务比宏任务先执行
  • promise的resolve是同步的,async遇到await之前是同步的,在执行await后面的代码,返回promise,await下面的代码放入微任务并退出。

22. js执行机制(event loop 事件循环)

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。

1)所有的同步任务都在主线程上执行,行成一个执行栈。

2)除了主线程之外,还存在一个任务列队event table,只要异步任务有了运行结果,Event Table会将这个函数移入Event Queue。

3)但是js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行宏任务中的微任务,将微任务放入eventqueue,但是两个queue不同。

4)主线程执行完毕后,会去微任务queue中读取,进入主线程执行,再从宏任务的queue读取。

5)重复上面三步。

24. 防抖和节流

防抖:n秒后在执行该事件,若在n秒内被重复触发,则重新计时(频繁操作点赞和取消点赞,搜索联想,)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function debounce2(fn, delay) {
let timer

return function () {
let args = arguments
if (timer) clearTimeout(timer)


let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) { fn(args) }
}
}

节流:n秒内只运行一次,若在n秒内重复触发,只有一次生效(鼠标不断点击触发)

1
2
3
4
5
6
7
8
9
10
11
12
13
function throttle2(fn, wait) {
let timer
return function () {
let _this = this
let args = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(_this, arguments)
}, wait);
}
}
}

函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行

26. 常用的数组方法(查)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var arr = ["李白", 123, false, undefined, null, true, 9, 5, 7, 13]
// 在数组末尾增加 push()
arr.push("剑仙")
console.log(arr);
// 在数组末尾删除 pop()
arr.pop()
console.log(arr);
// 在数组开头删除 shift()
arr.shift()
console.log(arr);
// 删除 splice()
arr.splice(1, 2)
console.log(arr);
// 修改 splice()
arr.splice(1, 0, "诗圣")
console.log(arr);
arr.splice(1, 3, 10)
console.log(arr);


// 数组转化为字符串 join() 产生一个新数组,不会改变原数组 以xx隔开
var arr1 = arr.join("1")
console.log(arr1);
// 合并两个数组 concat() 产生一个新数组,不会改变原数组
var arr3 = arr.concat(arr1)
console.log(arr3);
// 数组的截取 slice() 产生一个新数组,不会改变原数组
var arr4 = arr.slice(1, 3)
console.log(arr4);
// 数组的翻转 reverse() 产生一个新数组,不会改变原数组
var arr5 = arr.reverse()
console.log(arr5);
// 数组排序 sort() 产生一个新数组,不会改变原数组
var arr6 = arr.sort(function (a, b) {
return a - b
})
console.log(arr6);

1. 数组排序

1
2
3
4
5
6
7
8
9
10
11
12
var arr = [4, 1, 2, 3, 5];
for (var i = 0; i <= arr.length - 1; i++) {
for (var j = 0; j <= arr.length - i - 1; j++) {
if (arr[j] < arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}

}
}
console.log(arr);

2. 数组最大值

1. 冒泡排序
1
2
3
4
5
6
7
8
var a = [2, 5, 7, 343, 22, 23, 65, 234]
var max = 0
for (var i = 0; i < 8; i++) {
if (a[i] > a[i + 1]) {
max = a[i]
}
}
console.log(max);

3. 数组去重

1
2
3
4
5
6
7
8
9
10
var arr = [1, 2, 3, 4, 5, 2, 3, 4, 2, 23, 4, 5, 2, 2, 3, 54, 3, 3, 4]
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length - 1; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1)
j-- // 因为每次删除都会改变数组的长度,所以删了之后要是下标-1,之后再+1回到原来下标
}
}
}
console.log(arr);

4. 反转数组

1
2
3
4
5
6
var arr = ['red', 'green', 'blue', 'pink', 'purple', 'hotpink'];
var newArr = [];
for (var i = arr.length - 1; i >= 0; i--) {
newArr[newArr.length] = arr[i]
}
console.log(newArr);

5. 遍历数组

1. for of
  • 不同于forEach的是,for of 循环可以随时退出
1
2
3
4
var arr = ['red', 'green', 'blue', 'pink', 'purple', 'hotpink'];
for(let i of arr) {
console.log(i) //red green blue pink purplr hotpink
}
2. forEach
  • 可以通过return跳出本次循环,执行下一次循环
1
2
3
4
5
6
7
var arr = [1, 2, 3, 4, 5, 6]
arr.forEach((item) => {
if (item === 3) {
return
}
console.log(item) // 1 2 4 5 6
})
3. some
  • 不会对空数组进行检测
  • 不会改变原数组
  • 检测数组里的每一个值是否满足条件,如果有一个满足就返回true,否则返回false
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.some((item) => {
console.log(item) // 1 2 3
return item === 3
})
console.log(result) // true
4. every
  • 不会对空数组进行检测
  • 不会改变原数组
  • 检测数组里的每一个值是否满足条件,如果有一个不满足就返回false,否则返回true
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.every((item) => {
console.log(item) // 1
return item === 3
})
console.log(result) // false
5. map
  • 不会对空数组进行检测
  • 不会改变原数组
  • 按照原始数组元素顺序依次处理,返回一个新数组
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.map((item) => {
console.log(item) // 1 2 3 4 5 6
return item * item
})
console.log(result) // [1 4 9 16 25 36]
6. filter
  • 不会对空数组进行检测
  • 不会改变原数组
  • 对数组进行渲染,返回符合条件的数组
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.filter((item) => {
console.log(item) // 1 2 3 4 5 6
return item > 3
})
console.log(result) // [4,5,6]
7. find()
  • 不会对空数组进行检测

  • 不会改变原数组

  • 找到符合条件的第一项,没有找到返回undefined

  • var arr = [1, 2, 3, 4, 5, 6]
        const result = arr.find((item) => {
          console.log(item) // 1 2 3 4 5 6
          return item > 3
        })
        console.log(result) // 4
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37


    ### 27. 常用的字符串方法(查)

    ```js
    // 字符串的长度 length
    console.log(str1.length)
    // 通过下标获取某个字符 str.charAt()
    let str5 = '我是一个字符串'
    console.log(str5.charAt(3))
    // 截取字符串 str.slice()
    console.log(str5.slice(2, 4))

    // 返回字符串第一次出现的位置 没有出现过返回-1 str.indexOf()
    console.log(str5.indexOf('123'))
    console.log(str5.search('个'))
    // indexOf和search的区别 indexOf只能匹配字符串
    // search可以匹配 正则和字符串

    // 替换字符串 str.replace()
    let str6 = 'sldjfsljflskjfdslfjdsl'
    let str7 = str6.replace('s', '我的')
    console.log(str7, str6)

    // 分割字符串转化为数组 str.split()
    console.log(str6.split('s'))

    // 表单里面比较重要的的 str.trim()
    let str8 = ' 我的字符 '
    console.log(str8.length)
    console.log(str8.trim().length)

    // 判断字符串是否有某个字符 str.includes()
    console.log(str8.includes('我'))
    // 多一嘴 数组也是一样的方法
    let arr = [1, 2, 3, 45, 3, 1, '123', 12]
    console.log(arr.includes(123))

28. bom和dom的区别

  • bom就是window,包含windows(窗口)、navigator(浏览器)、screen(浏览器屏幕)、history(访问历史)、location(地址)等,浏览器相关的东西。bom是包含dom的。

  • dom是document, html相关的都在里面

29. JS性能优化的方式

  • 垃圾回收
  • 防抖节流
  • 分批加载(setInterval,加载10000个节点)
  • 事件委托
  • requestAnimationFrame的使用
  • script标签中的defer和async
  • CDN(CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求)

30. defer 和 async

  • 浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到