前端面试题汇总(完善中)

目录

一、HTML篇

1.HTML5新特征

2.sessionStorage和localStorage的区别

 3.BFC 是什么?

二、CSS篇

1.css有哪些基本的选择器,执行先后顺序?

2.水平垂直居中DIV

3.清除浮动

4.CSS3新特征

5.介绍一下盒模型

6.CSS中有哪些长度单位?

7.display:none和visibility:hidden的区别

8. 回流(重排)和重绘是什么?如何避免?

9.如果要做优化,CSS提高性能的方法有哪些?

10.简明说一下 CSS link 与 @import 的区别和用法?

12. 常见兼容性问题?

浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.

三、JS 篇

1. JS 数据类型 ?数据类型主要包括两部分:

2. 判断一个值是什么类型有哪些方法?

3. null 和 undefined 的区别?null 表示一个对象被定义了,值为“空值”;undefined 表示不存在这个值。(1)变量被声明了,但没有赋值时,就等于undefined。 (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时,默认返回undefined。

4. 怎么判断一个变量arr的话是否为数组(此题用 typeof 不行)?

Array.isArray(arr) arr instanceof Arrayarr.constructor == ArrayObject.protype.toString.call(arr) ===‘[Object Array]’

5. “ ===”、“ ==”的区别?

6. “eval是做什么的?

7. 箭头函数有哪些特点?

8. var、let、const 区别?

9. 介绍一下闭包和闭包常用场景?闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式,就是在一个函数的内部创建另一个函数使用闭包主要为了设计私有的方法和变量,闭包的优点是可以避免变量的污染,在js中,函数即闭包,只有函数才会产生作用域的概念。应用场景,设置私有变量的方法不适用场景:返回闭包的函数是个非常大的函数

13. js防抖和节流?

13. documen.write 和 innerHTML 的区别?

14.js中操作数组的方法?

20. 请解释一下 JavaScript 的同源策略?概念:同源策略是客户端脚本(尤其是Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。指一段脚本只能读取来自同一来源的窗口和文档的属性。

28. Promise

1.什么是Promise

Vue篇

2. 为什么data是一个函数

3、Vue组件通讯有哪些方式?

4. Vue 的父组件和子组件生命周期钩子函数执行顺序?


一、HTML篇

1.HTML5新特征

Canvas绘图以及SVG绘图。
拖放(Drag and drop)API
语义化标签(header、nav、footer、article、section)
音频、视频(audio、video)API
地理定位(Geolocation)
本地离线存储(localStorage),长期存储数据,关闭浏览器后不丢失。
会话储存(sessionStorage),数据在关闭浏览器后自动删除。
表单控件(calendar、date、time、email、url、search)
新技术如Web Worker、Web Socket。(关于Web Socket使用可以看这篇文章浅谈Web Socket。)
 

2.sessionStorage和localStorage的区别

sessionStoragelocalStorage 都是 HTML5 提供的 Web 存储 API,它们都可以在浏览器端存储数据,但有以下区别:

  • 生命周期不同:sessionStorage 的生命周期仅限于当前会话,当用户关闭浏览器窗口或标签页时,数据就会被删除。而 localStorage 的生命周期则是永久的,除非用户手动清除或者网站清除。

  • 存储大小不同:sessionStoragelocalStorage 的存储大小都是根据浏览器的不同而不同。但是一般是5mb。

  • 作用域不同:sessionStoragelocalStorage 的作用域也不同。sessionStorage 仅在当前窗口或标签页中有效,而 localStorage 则在所有同源窗口或标签页中都有效。

总之,如果你需要在用户会话期间存储一些数据,可以使用 sessionStorage,而如果你需要永久存储一些数据,则可以使用 localStorage

 3.BFC 是什么?

BFC 即 Block Formatting Contexts (块级格式化上下文),它属于普通流,即:元素按照其在 HTML 中的先后位置至上而下布局,在这个过程中,行内元素水平排列,直到当行被占满然后换行,块级元素则会被渲染为完整的一个新行,除非另外指定,否则所有元素默认都是普通流定位,也可以说,普通流中元素的位置由该元素在 HTML 文档中的位置决定。
可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。
只要元素满足下面任一条件即可触发 BFC 特性

body 根元素
浮动元素:float 除 none 以外的值
绝对定位元素:position (absolute、fixed)
display 为 inline-block、table-cells、flex
overflow 除了 visible 以外的值 (hidden、auto、scroll)
 

二、CSS篇

1.css有哪些基本的选择器,执行先后顺序?

类选择器(class)、标签选择器、ID选择器

!important > 行内样式(比重1000)> ID 选择器(比重100) > 类选择器(比重10) > 标签(比重1) > 通配符 > 继承 > 浏览器默认属性

2.水平垂直居中DIV

单行文本: line-height = height
图片: vertical-align: middle;

元素:

1.使用 flexbox 布局

.container { display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ }

2.使用绝对定位和 transform 属性

.container { position: relative; } .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /* 水平垂直居中 */ }

其中,.container 是包裹 div 元素的容器,.child 是需要水平垂直居中的 div 元素。

3.使用 table-cell 布局

.container { display: table-cell; text-align: center; /* 水平居中 */ vertical-align: middle; /* 垂直居中 */ }

4.使用 grid 布局

.container { display: grid; place-items: center; /* 水平垂直居中 */ }

其中,.container 是包裹 div 元素的容器。这些方法都可以实现 div 元素的水平垂直居中。

3.清除浮动

1.父级div定义overflow:hidden(如果父级元素有定位元素超出父级,超出部分会隐藏,)
2.给浮动元素父级增加标签(由于新增标签会造成不必要的渲染,不建议使用)
3.伪元素清除浮动:给浮动元素父级增加 .clearfix::after(content: ‘’; display: table; clear: both;)(不会新增标签,不会有其他影响,)
 

4.CSS3新特征

圆角(border-radius)
阴影(box-shadow)
文字特效(text-shadow)
线性渐变(gradient)
变换(transform)
更多的CSS选择器
更多背景设置(background)
色彩模式(rgba)
伪元素(::selection)
媒体查询(@media)
多栏布局(column)
图片边框(border-image)

5.介绍一下盒模型

在 CSS 中,盒模型有两种不同的模式:标准盒模型和怪异盒模型。

1.标准盒模型

标准盒模型是指盒子的大小仅包括内容区域的大小,不包括边框和内边距。

box-sizing: content-box;

2.怪异盒模型

怪异盒模型是指盒子的大小包括了内容区域、内边距和边框的大小。

box-sizing: border-box;

在实际开发中,我们可以通过设置 box-sizing 属性来切换标准盒模型和怪异盒模型。如果需要将一个元素从标准盒模型转换为怪异盒模型

6.CSS中有哪些长度单位?

1.绝对长度单位:px。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。
一般电脑的分辨率有{19201024}等不同的分辨率
1920
1024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

2.百分比: %

3.em:相对于父元素的字体大小,如果自身没有设置字体大小,则相对于父元素的默认字体大小。例如,如果父元素的字体大小为 16px,子元素设置了 font-size: 1.5em;,则子元素的字体大小为 24px(1.5 * 16px)。

4.rem:相对于根元素(即 html 元素)的字体大小。例如,如果根元素的字体大小为 16px,子元素设置了 font-size: 1.5rem;,则子元素的字体大小为 24px(1.5 * 16px)。

5.vw:相对于视口宽度的百分比。例如,如果视口宽度为 1000px,元素设置了 width: 50vw;,则元素的宽度为 500px(50% * 1000px)

6.vh:相对于视口高度的百分比。例如,如果视口高度为 800px,元素设置了 height: 50vh;,则元素的高度为 400px(50% * 800px)。

需要注意的是,emrem 都是相对单位,而 vwvh 是绝对单位。在实际开发中,可以根据具体情况选择不同的长度单位来实现布局效果。

7.display:none和visibility:hidden的区别

display:none:隐藏元素,在文档布局中不在给它分配空间(从文档中移除),它各边的元素会合拢,就当他从来不存在,会引起回流(重排)。
visibility:hidden: 隐藏元素,但是在文档布局中仍保留原来的空间(还在文档中),不会引起回流(重绘)。

8. 回流(重排)和重绘是什么?如何避免?

1.重绘
简单来说就是重新绘画,当给一个元素更换颜色、更换背景,虽然不会影响页面布局,但是颜色或背景变了,就会重新渲染页面,这就是重绘。

2.回流(重排)
当增加或删除dom节点,或者给元素修改宽高时,会改变页面布局,那么就会重新构造dom树然后再次进行渲染,这就是回流。

总结
重绘不会引起dom结构和页面布局的变化,只是样式的变化,有重绘不一定有回流。
回流则是会引起dom结构和页面布局的变化,有回流就一定有重绘。
不管怎么说都是会影响性能。
怎么进行优化或减少?
1.多个属性尽量使用简写,例如:boder可以代替boder-width、boder-color、boder-style

2.避免频繁操作 DOM:每次修改 DOM 都会导致浏览器进行重排或重绘,因此应该尽量避免频繁修改 DOM。可以通过创建文档片段、使用 innerHTML 等方式来批量更新 DOM。

3.使用 CSS3 动画代替 JavaScript 动画:CSS3 动画可以通过 GPU 加速,性能更高,而 JavaScript 动画则需要通过 JavaScript 计算每一帧的 CSS 样式,性能较差。

4.使用 transform 和 opacity 属性代替 top 和 left 属性:使用 transform 和 opacity 属性可以避免触发重排和重绘,而 top 和 left 属性则会导致元素的位置发生变化,需要重新计算布局。

5.避免使用 table 布局:table 布局会导致整个表格重新布局,影响性能。

6.避免在循环中计算样式:在循环中计算样式会导致频繁触发重排和重绘,应该尽量避免这种情况。

9.如果要做优化,CSS提高性能的方法有哪些?

  1. 使用压缩的 CSS 文件:压缩 CSS 文件可以减小文件大小,加快页面加载速度。

  2. 避免使用 @import:@import 会导致浏览器发起额外的请求,影响页面加载速度,应该尽量避免使用。 @import url('style.css');

  3. 合并 CSS 文件:将多个 CSS 文件合并成一个文件可以减少 HTTP 请求次数,提高页面加载速度。

  4. 使用浏览器缓存:设置适当的缓存策略可以减少 HTTP 请求次数,加快页面加载速度。

  5. 避免使用 !important:!important 会覆盖其他样式,导致样式冲突,应该尽量避免使用。

  6. 避免使用过多的层级选择器:层级选择器会增加样式匹配的复杂度,影响页面渲染速度,应该尽量避免使用。

  7. 避免使用通配符选择器:通配符选择器会匹配所有元素,增加样式匹配的复杂度,影响页面渲染速度,应该尽量避免使用。

  8. 使用字体图标代替图片:字体图标可以减小文件大小,加快页面加载速度。

  9. 使用 CSS3 动画代替 JavaScript 动画:CSS3 动画可以通过 GPU 加速,性能更高,而 JavaScript 动画则需要通过 JavaScript 计算每一帧的 CSS 样式,性能较差

10.简明说一下 CSS link 与 @import 的区别和用法?

CSS 中的 link 和 @import 都可以用来引入外部样式表,但它们有以下几个区别:

1.加载顺序

使用 link 标签引入的外部样式表会在页面加载时同时加载,而 @import 引入的外部样式表会在页面加载完毕后再加载。

2.兼容性

@import 是 CSS2.1 提供的语法,因此在老的浏览器中可能不被支持。而 link 标签是 HTML4 的标签,兼容性更好。

3.作用域

使用 link 标签引入的外部样式表会覆盖当前页面的所有样式,而 @import 引入的外部样式表只会覆盖当前 @import 之前的样式。

4.功能

link 标签除了可以加载样式表外,还可以用来指定页面之间的关系,如 rel="next"、rel="prev" 等。而 @import 只能用来加载样式表。

使用 link 标签引入外部样式表的语法如下:

<link rel="stylesheet" href="style.css">

其中,rel 属性指定了链接的关系为样式表,href 属性指定了外部样式表的 URL。

11.画一条0.5px的直线?

height: 1px;
transform: scale(0.5);

12. 常见兼容性问题?

浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一。
Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,
可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决.


三、JS 篇

1. JS 数据类型 ?
数据类型主要包括两部分:

基本数据类型: Undefined、Null、Boolean、Number 和 String
引用数据类型: Object (包括 Object 、Array 、Function)
ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的数据类型 )

为什么需要使用Symbol?
在ES6之前,对象的属性名都是字符串形式,很容易造成属性名的冲突;

比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;

Symbol就是为了解决上面的问题,来生成一个独一无二的值。

Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值。
Symbol即使多次创建值,它们也是不同的。
 

2. 判断一个值是什么类型有哪些方法?

  • 使用 typeof 运算符:可以返回一个字符串,表示值的数据类型。可以判断 JavaScript 的基本数据类型,包括 numberstringbooleanundefinedfunction,但对于 null 和对象类型的判断不够准确。例如:typeof 123 返回 "number"。typeof 123; // "number"
    typeof "hello"; // "string"
    typeof true; // "boolean"
    typeof undefined; // "undefined"
    typeof null; // "object"
    typeof {}; // "object"
    typeof []; // "object"
    typeof function(){}; // "function"

  • 使用 instanceof 运算符:可以判断一个对象是否是某个构造函数的实例,但不能判断基本数据类型和内置对象类型。。例如:[] instanceof Array 返回 true。[] instanceof Array; // true
    new Date() instanceof Date; // true
    "hello" instanceof String; // false

  • constructor 属性指向对象的构造函数,可以通过它来判断对象的数据类型。在上面的示例中,我们创建了不同类型的变量,并使用 constructor 属性来判断它们的数据类型。

    需要注意的是,对于基本数据类型(如字符串、数字、布尔值等),不能直接使用 constructor 属性来判断其数据类型,因为它们并不是对象。如果需要判断基本数据类型,可以使用 typeof 运算符。

    var arr = [1, 2, 3];
    var obj = {name: "Tom"};
    var fun = function(){};

    console.log(arr.constructor === Array); // true
    console.log(obj.constructor === Object); // true
    console.log(fun.constructor === Function); // true
     

  • 使用 Object.prototype.toString() 方法:可以返回一个表示对象类型的字符串,但需要使用 call() 方法来改变 this 的指向,而且返回的字符串格式不够直观。例如:Object.prototype.toString.call([]) 返回 "[object Array]"

3. null 和 undefined 的区别?
null 表示一个对象被定义了,值为“空值”;
undefined 表示不存在这个值。
(1)变量被声明了,但没有赋值时,就等于undefined。 (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。 (3)对象没有赋值的属性,该属性的值为undefined。 (4)函数没有返回值时,默认返回undefined。

4. 怎么判断一个变量arr的话是否为数组(此题用 typeof 不行)?

Array.isArray(arr) 
arr instanceof Array
arr.constructor == Array
Object.protype.toString.call(arr) ===‘[Object Array]’

5. “ ===”、“ ==”的区别?

==,当且仅当两个运算数相等时,它返回 true,即不检查数据类型
===,只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型

6. “eval是做什么的?

它的功能是把对应的字符串解析成 JS 代码并运行;
应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。

7. 箭头函数有哪些特点?

  1. 更简洁的语法:箭头函数的语法比普通函数更简洁,可以让代码更加简洁易读。

  2. 没有 this 绑定:箭头函数没有自己的 this 绑定,它的 this 值继承自外层作用域。这意味着在箭头函数中使用 this 时,它指向的是定义函数时所在的对象,而不是调用函数时所在的对象。

    function Person(name) {
      this.name = name;
      this.sayHello = () => {
        console.log(`Hello, my name is ${this.name}`);
      };
    }

    const person1 = new Person("Alice");
    person1.sayHello(); // 输出:Hello, my name is Alice

    const person2 = new Person("Bob");
    person2.sayHello(); // 输出:Hello, my name is Bob

    在上面的示例中,我们定义了一个 Person 构造函数,它创建了一个包含 name 属性和 sayHello 方法的对象。sayHello 方法是一个箭头函数,因此它的 this 值继承自外层作用域,即定义函数时所在的对象。

    当我们创建两个不同的 Person 实例时,它们的 sayHello 方法中的 this.name 分别指向它们自己的 name 属性,而不是共享同一个 name 属性。这是因为在箭头函数中,this 值是静态绑定的,不会随着函数调用的上下文改变而改变。

  3. 没有 arguments 对象:箭头函数也没有自己的 arguments 对象,它的参数列表可以使用 rest 参数来代替。在 JavaScript 中,函数有一个内置的 arguments 对象,它包含了函数调用时传入的所有参数。例如:

    function sum() {
      let result = 0;
      for (let i = 0; i < arguments.length; i++) {
        result += arguments[i];
      }
      return result;
    }

    console.log(sum(1, 2, 3)); // 输出:6

    在上面的示例中,我们定义了一个 sum 函数,它使用 arguments 对象来计算传入参数的总和。

    然而,箭头函数没有自己的 arguments 对象,因此不能直接使用 arguments 对象来获取传入参数。相反,箭头函数使用 rest 参数来代替 arguments 对象。rest 参数是一个以三个点(...)开头的参数,它可以接收任意数量的参数,并将它们作为一个数组传递给函数。例如:

    const sum = (...args) => {
      let result = 0;
      for (let arg of args) {
        result += arg;
      }
      return result;
    };

    console.log(sum(1, 2, 3)); // 输出:6
    在上面的示例中,我们使用 rest 参数 ...args 来接收传入的参数,并计算它们的总和。这种方式比使用 arguments 对象更加直观和易读。

  4. 不能用作构造函数:箭头函数不能用作构造函数,因为它没有自己的 this 绑定。

8. var、let、const 区别?

var定义的变量,变量提升,没有块的概念,可以跨块访问。
let定义的变量,只能在块作用域里访问,不能声明同名变量。
const用来定义常量,使用时必须初始化(即必须赋值),不能声明同名变量,只能在块作用域里访问,而且不能修改,但是在定义的对象时对象属性值可以改变。
他们都不能跨函数访问

const 定义的变量可以修改吗?

const 定义的基本类型不能改变,但是定义的对象是可以通过修改对象属性等方法改变的

基本数据类型的值直接在栈内存中存储,值与值之间是独立存在的,修改一个变量不会影响其他的变量。

而引用数据类型的值存储在内存中的堆区域中,并且变量实际上只是一个指向该内存地址的引用(指针)而不是对象本身。

在 JavaScript 中,使用 const 关键字声明的变量是常量,即它们的值是不可变的。这意味着,对于基本数据类型的常量,我们不能修改它们的值,

例如:const a = 1;
a = 2; // 报错:Assignment to constant variable.

然而,对于引用数据类型的常量,虽然常量本身的值是不可变的,但是它们所指向的对象或数组是可以改变的。例如:

const obj = { name: "Alice" };
obj.name = "Bob"; // 可以修改对象属性
console.log(obj); // 输出:{ name: "Bob" }

const arr = [1, 2, 3];
arr.push(4); // 可以修改数组元素
console.log(arr); // 输出:[1, 2, 3, 4]

9. 介绍一下闭包和闭包常用场景?
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包常见方式,就是在一个函数的内部创建另一个函数
使用闭包主要为了设计私有的方法和变量,闭包的优点是可以避免变量的污染,在js中,函数即闭包,只有函数才会产生作用域的概念。
应用场景,设置私有变量的方法
不适用场景:返回闭包的函数是个非常大的函数

闭包示例:

function outerFunction() {
  var outerVariable = "I am outside!";

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

var innerFunc = outerFunction();
innerFunc(); // 输出 "I am outside!"
 

闭包的优点:

  • 闭包可以访问和操作其外部函数作用域中的变量,使得函数更加灵活和可重用。
  • 闭包可以创建私有变量和方法,这些变量和方法只能在函数内部访问,从而提高了代码的安全性和可靠性。
  • 闭包可以实现柯里化和函数式编程等高级编程技巧,使得代码更加简洁和易于维护。

闭包的缺点:

  • 闭包会占用更多的内存空间,因为它需要保存其外部函数作用域中的变量和函数。
  • 闭包可能导致内存泄漏,因为在某些情况下,闭包会持有对外部函数作用域中对象的引用,从而使这些对象无法被垃圾回收。
  • 闭包可能会导致意外的副作用,因为它可以访问和修改其外部函数作用域中的变量,从而影响到其他代码的行为。

柯里化(Currying)是指将一个接受多个参数的函数转化为一系列只接受一个参数的函数的过程。这样做的好处是可以将函数的复杂度降低,使得代码更加简洁和易于维护。

函数式编程是一种编程范式,它将函数视为一等公民,并强调函数的纯粹性和不可变性。函数式编程通常使用高阶函数、柯里化、闭包等技术来实现。

以下是一个使用闭包实现柯里化和函数式编程的例子:

function add(x) {
  return function(y) {
    return x + y;
  }
}

var add5 = add(5); // 返回一个只接受一个参数的函数
console.log(add5(3)); // 输出 8

// 使用柯里化实现一个通用的加法函数
function addCurry(x) {
  return function(y) {
    if (y === undefined) {
      return x;
    } else {
      return addCurry(x + y);
    }
  }
}

console.log(addCurry(2)(3)(4)()); // 输出 9

在这个例子中,我们定义了一个 add 函数,它返回一个只接受一个参数的函数。这个返回的函数使用了闭包来保存 x 的值,并在调用时将其与 y 相加。

我们还定义了一个 addCurry 函数,它使用了柯里化和闭包来实现一个通用的加法函数。这个函数返回一个只接受一个参数的函数,如果这个参数是 undefined,则返回当前的总和,否则将当前的总和与参数相加,并返回一个新的只接受一个参数的函数。这样,我们就可以使用 addCurry 来实现任意数量的加法,例如 addCurry(2)(3)(4)() 就等于 2 + 3 + 4

10. javascript的内存(垃圾)回收机制?


垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存


一般使用标记清除方法(mark and sweep),

(1)当变量进入执行环境时(函数中声明变量),就标记这个变量为“进入环境”,当变量离开环境时(函数执行结束),则将其标记为“离开环境”,离开环境之后还有的变量则是需要被删除的变量。

(2)垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记。

(3)去掉环境中的变量以及被环境中变量引用的变量的标记。

(4)之后再被加上标记的变量即是需要回收的变量(因为环境中的变量已经无法访问到这些变量)

(5)最后,垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
还有引用计数方法(reference counting), 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。

什么情况会引起内存泄漏?
虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏。

1.    意外的全局变量引起的内存泄漏。

原因:全局变量,不会被回收。

解决:使用严格模式避免。

 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用。

2.    闭包引起的内存泄漏

原因:闭包可以维持函数内局部变量,使其得不到释放。

解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

3.    没有清理的DOM元素引用

原因:虽然别的地方删除了,但是对象中还存在对dom的引用

解决:手动删除。

4.    被遗忘的定时器或者回调

原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。

解决:手动删除定时器和dom。

5.    子元素存在引用引起的内存泄漏

原因:div中的ul li  得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。

解决:手动删除清空。
这些引用不清空就会一直存在内存中。

11. JavaScript原型,原型链 ? 有什么特点?


任何对象都有 proto 隐式原型, 等于 构造函数 的 prototype
const obj = {}
obj.__proto__ === Object.prototype // true
任何函数都有 prototype 显示原型 等于 原型对象(就是一个普通对象包含公共属性)
*(通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性)

function Person () {}
Person.prototype = 原型对象
Person.prototype.constructor === Person // true

const person1 = new Person()
person1.__proto__ === Person.prototype // true
person1.constructor == Person // true
 

对象还具有 constructor 属性,指向构造函数(Person.prototype.constructor == Person)
原型链是依赖于__proto__, 查找一个属性会沿着 proto 原型链向上查找,直到找到为止。
特殊
// 原型链最终点是 null 
Object.prototype.__proto__ === null // true
obj.__proto__.__proto__ === null // true
每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,
于是就这样一直找下去,也就是我们平时所说的原型链的概念。
关系:instance.constructor.prototype = instance.proto
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

12.如何实现继承?

好的,下面是几个 JavaScript 中继承的示例,以及它们的优缺点。

1. 原型链继承

原型链继承是 JavaScript 中最常见的继承方式,它的基本思路是将父类的实例作为子类的原型。具体实现方式如下:

```
function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log('Hello, I am ' + this.name);
}

function Dog(name, color) {
  this.color = color;
}

Dog.prototype = new Animal();

const dog = new Dog('Wangwang', 'white');
dog.sayHello(); // 输出:Hello, I am Wangwang
```

优点:

- 父类的属性和方法可以被子类继承和共享。
- 可以通过修改父类原型上的方法和属性,达到一次修改多处生效的效果。

缺点:

- 子类无法向父类构造函数传参。
- 父类的引用属性会被所有子类实例共享,容易造成数据污染。
- 子类无法重写从父类继承下来的方法,只能通过修改父类原型上的方法实现。

2. 构造函数继承

构造函数继承是通过在子类中调用父类的构造函数来实现的,这种方式可以避免子类实例共享父类实例的问题。具体实现方式如下:

```
function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log('Hello, I am ' + this.name);
}

function Dog(name, color) {
  Animal.call(this, name);
  this.color = color;
}

const dog = new Dog('Wangwang', 'white');
console.log(dog.name); // 输出:Wangwang
console.log(dog.color); // 输出:white
dog.sayHello(); // TypeError: dog.sayHello is not a function
```

优点:

- 可以避免子类实例共享父类实例的问题。

缺点:

- 父类的属性和方法无法被子类继承和共享。
- 子类无法访问到父类原型上的方法。

3. 组合继承

组合继承是将原型链继承和构造函数继承结合起来的一种继承方式,它既可以避免子类实例共享父类实例的问题,又可以继承父类原型上的方法。具体实现方式如下:

```
function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log('Hello, I am ' + this.name);
}

function Dog(name, color) {
  Animal.call(this, name);
  this.color = color;
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

const dog = new Dog('Wangwang', 'white');
console.log(dog.name); // 输出:Wangwang
console.log(dog.color); // 输出:white
dog.sayHello(); // 输出:Hello, I am Wangwang
```

优点:

- 可以避免子类实例共享父类实例的问题。
- 子类可以访问到父类原型上的方法和属性。

缺点:

- 在继承父类构造函数时,会调用两次父类构造函数,造成了不必要的性能浪费。

4. ES6 中的 class 继承

ES6 中引入了 class 关键字,可以用来定义类和继承。class 继承本质上也是通过原型链来实现的。具体实现方式如下:

```
class Animal {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log('Hello, I am ' + this.name);
  }
}

class Dog extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
}

const dog = new Dog('Wangwang', 'white');
console.log(dog.name); // 输出:Wangwang
console.log(dog.color); // 输出:white
dog.sayHello(); // 输出:Hello, I am Wangwang
```

优点:

- 语法简洁易懂。
- 可以使用 super 关键字访问父类的属性和方法。
- 可以使用 extends 关键字继承父类。

缺点:

- 不支持多重继承。
- 无法继承非可枚举属性。
- 在继承父类构造函数时,会调用两次父类构造函数,造成了不必要的性能浪费。
 
以上是 JavaScript 中几种常见的继承方式及其优缺点,实际使用时需要根据需求和场景选择适合的继承方式。

13. new操作符具体干了什么呢

在 JavaScript 中,new 操作符用于创建一个对象实例。它的具体步骤如下:

  1. 创建一个空对象;
  2. 将这个空对象的原型指向构造函数的 prototype 属性;
  3. 将构造函数的 this 绑定到这个空对象上;
  4. 执行构造函数内部的代码,初始化这个对象;
  5. 如果构造函数返回的是一个对象,则返回该对象;否则返回第一步创建的对象。

例如,我们可以通过以下方式使用 new 操作符创建一个对象实例:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person = new Person("Alice", 20);
console.log(person); // 输出:Person { name: "Alice", age: 20 }

在上面的示例中,我们定义了一个 Person 构造函数,它接受两个参数 nameage,并将它们分别赋值给新创建的对象的 nameage 属性。然后,我们使用 new 操作符创建了一个 Person 对象实例,并将其赋值给变量 person。最后,我们输出了这个对象实例,可以看到它的属性值为 { name: "Alice", age: 20 }

需要注意的是,通过 new 操作符创建的对象实例与构造函数本身没有任何关系,它们只是共享同一个原型。因此,如果我们在构造函数内部修改了原型上的属性或方法,那么通过 new 操作符创建的所有对象实例都会受到影响。

14. JavaScript(ES6)的深拷贝与浅拷贝

深拷贝和浅拷贝是变量在内存中的存放位置不同(堆,栈)
在js中,对象和数组这种复杂的数据结构是放在堆中,堆的地址放在栈中
js中的number,string,bool简单类型是放在栈中
栈的存取速度比堆快,但是不适合存入复杂数据结构
一般直接存放在栈里的变量对应深拷贝,放在堆里的变量是浅拷贝


深拷贝:

 实质:深拷贝就是完全复制一份地址,完全是开辟了一个新的空间,如果改变原变量,则不会影响深拷贝出来的变量

  • 如果变量存放在栈中,则直接在栈里开辟一个新空间,存放该值
  • 如果变量存放在堆中,则在堆中开辟一个空间,在栈中开辟一个空间来存放推的地址

演示两个深拷贝的例子

var num=3
var numCopy=num
num=4
console.log("num:",num) //4
console.log("numCopy:",numCopy) //3
 
var obj={
  name:'looper'
}
var objCopy=JSON.parse(JSON.stringify(obj))
obj.name="zhuo"
console.log("obj:",obj) //"zhuo"
console.log("objCopy:",objCopy) //"looper"

浅拷贝:

实质:浅拷贝只针对放在堆里的变量,目前最多的是对象和数组,拷贝一份变量之后,该变量的的内存地址也是指向被copy的变量地址,简单来说就是浅拷贝出来的变量和被拷贝的变量使用同一个堆的值,任何一个变量的属性值改变会影响另外一个变量,深拷贝则不会,对于“=”就是一个浅拷贝

var obj={
  name:'looper'
}
var objCopy=obj
obj.name="zhuo"
console.log("obj:",obj) //"zhuo"
console.log("objCopy:",objCopy) //"zhuo"

深拷贝的实现方法:

 1.使用JSON

 JSON.parse(JSON.stringify(obj))

  2.使用递归

function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let newObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    newObj[key] = deepClone(obj[key]);
  }
  return newObj;
}

let a = {
  q:1,
  w:2,
  e:{
    r:2
  },
  arr:[1]
};

let b = deepClone(a);
console.log(b); // Output: {q: 1, w: 2, e: {r: 2}, arr: [1]}
console.log(b.e === a.e); // : false
console.log(b.arr === a.arr); // : false

在这个例子中,我们定义了一个deepClone函数,它接受一个对象作为参数。如果该参数不是对象或者是null,直接返回该参数。否则,创建一个新的空对象或空数组,并遍历原始对象或数组的每个属性或元素,将其递归拷贝到新的对象或数组中。最后返回新的对象或数组。

使用这个函数,我们可以对该对象的每一层进行深拷贝。例如,我们可以将该对象拷贝到变量b中,并检查b.eb.arra.ea.arr是否是同一个对象。由于我们使用了深拷贝,因此它们应该是不同的对象。

可以实现二级数组或对象深拷贝

3.扩展运算符:会深拷贝数据结构的第一层数据,第二层的数据是浅拷贝,举个例子

let a = {
  q:1,
  w:2,
  e:{
    r:2
  },
  arr:[1]
};
let b = Object.assign({}, a);
console.log(b); // Output: {q: 1, w: 2, e: {r: 2}, arr: [1]}
console.log(b.e === a.e); // true
console.log(b.arr === a.arr); // true

 

4.Object.assign():Object.assign()方法可以将多个对象合并到一个新对象中,并返回新对象。因此,我们可以使用Object.assign()方法将原始对象合并到一个空对象中,从而实现深拷贝。

let a = {
  q:1,
  w:2,
  e:{
    r:2
  },
  arr:[1]
};
let b = Object.assign({}, a);
console.log(b); // Output: {q: 1, w: 2, e: {r: 2}, arr: [1]}
console.log(b.e === a.e); // true
console.log(b.arr === a.arr); // true

15. js防抖和节流

单位时间内,频繁触发一个事件,以最后一次触发为准。
防抖的实现
1.声明一个全部变量存储定时器ID。
2.每一次触发交互的时候,先清除上一次的定时器,然后开启本次定时器。

      //输入框事件
      let timeID = null
      document.querySelector('input').oninput = function () {
        //1先清除之前的定时器
        clearTimeout(timeID)
        //2.开启本次定时器
        timeID = setTimeout(() => {
          console.log(`发送ajax,搜索的内容是${this.value}`)
        }, 500)
      }
函数节流

单位时间内,频繁触发一个事件,只会触发一次。
节流的实现
1.声明一个全局变量存储触发事件。
2.每一次触发事件,获取当前时间。
3.判断当前时间与上一次触发事件,是否超过了间隔。
4.如果超过间隔时间,则执行事件处理代码,然后存储本次触发的时间。

    //声明一个全局变量存储触发时间
    let lastTime = null
    //页面滚动事件
    window.onscroll = function () {
      //1.每一次触发 先获取本次时间戳
      let currentTime = Date.now()
      //2.判断当前时间 与 上次触发时间 是否超过间隔
      if (currentTime - lastTime >= 500) {
        console.log(document.documentElement.scrollTop)//获取滚动距离
        //3.存储本次的触发时间
        lastTime = currentTime
      }
    }
 

16. documen.write 和 innerHTML 的区别?

document.write 只能重绘整个页面
innerHTML 可以重绘页面的一部分

17.js中操作数组的方法?

JavaScript提供了许多操作数组的方法,包括以下常见方法:

1. push:向数组末尾添加一个或多个元素,并返回新的数组长度。

const arr = [1, 2, 3]

arr.push(4, 5)

console.log(arr) // [1, 2, 3, 4, 5]

2. pop:从数组末尾删除一个元素,并返回该元素的值。

const arr = [1, 2, 3]

const last = arr.pop()

console.log(last) // 3

console.log(arr) // [1, 2]

3. unshift:向数组开头添加一个或多个元素,并返回新的数组长度。

const arr = [1, 2, 3]

arr.unshift(0, -1)

console.log(arr) // [-1, 0, 1, 2, 3]

4. shift:从数组开头删除一个元素,并返回该元素的值。

const arr = [1, 2, 3]

const first = arr.shift()

console.log(first) // 1

console.log(arr) // [2, 3]

5. splice:从数组中添加或删除元素,并返回被删除的元素组成的数组。

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

const removed = arr.splice(1, 2, 'a', 'b')

console.log(removed) // [2, 3]

console.log(arr) // [1, 'a', 'b', 4, 5]

6. slice:从数组中截取一部分元素,并返回一个新的数组。

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

const sliced = arr.slice(1, 4)

console.log(sliced) // [2, 3, 4]

console.log(arr) // [1, 2, 3, 4, 5]

7. concat:将两个或多个数组合并成一个新的数组。

const arr1 = [1, 2, 3]

const arr2 = [4, 5, 6]

const combined = arr1.concat(arr2)

console.log(combined) // [1, 2, 3, 4, 5, 6]

8. map(es6):对数组中的每个元素执行一个函数,并返回一个新的数组。

const arr = [1, 2, 3]

const squared = arr.map(x => x * x)

console.log(squared) // [1, 4, 9]

9.set(es6):ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值

 var arr = [12,12,3,45,56,34,23,42,3,4,4,45]

console.log(new Set(arr)); //Set(8)

 var arr2 =[...new Set(arr)]
 console.log(arr2); //[12,3,45,56,34,23,42,4]

 var s1 =new Set([1,88,8,1,3])
 //添加
 s1.add('love')
 //删除
 s1.delete(8)
 //检测
 console.log(s1.has('love')); //true
 //长度
 console.log('长度',s1.size,s1); //长度 4 Set(4)

10. forEach(es6):对数组中的每个元素执行一个函数,没有返回值。

const arr = [1, 2, 3]

arr.forEach(item => console.log(item))

// 1

// 2

// 3

11. filter(es6):对数组中的每个元素执行一个函数,返回一个新的数组,其中包含返回值为true的元素。

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

const even = arr.filter(item => item % 2 === 0)

console.log(even) // [2, 4]

12. find(es6):返回数组中第一个返回值为true的元素,如果没有找到则返回undefined。

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

const even = arr.find(item => item % 2 === 0)

console.log(even) // 2

13.findIndex(es6):返回数组中第一个返回值为true的元素的索引,如果没有找到则返回-1。

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

const index = arr.findIndex(item => item > 3)

console.log(index) // 3

14. reduce(es6):对数组中的元素执行一个函数,返回一个累加值。

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

const sum = arr.reduce((prev, curr) => prev + curr, 0)

console.log(sum) // 15

15.some(es6):对数组中的每个元素执行一个函数,如果有一个返回值为true,则返回true,否则返回false。

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

const hasEven = arr.some(item => item % 2 === 0)

console.log(hasEven) // true

16. every(es6):对数组中的每个元素执行一个函数,如果所有元素返回值均为true,则返回true,否则返回false。

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

const allPositive = arr.every(item => item > 0)

console.log(allPositive) // true

17. sort:对数组中的元素进行排序。

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

arr.sort((a, b) => a - b)

console.log(arr) // [1, 2, 3, 4, 5]

18.Array.from(es6)静态方法

Array.from()方法主要用于将两类对象(类似数组的对象[array-like object]和可遍历对象[iterable])转为真正的数组

//类似数组的对象转为真正的数组

let arrayLike = {

'0': 'a',

'1': 'b',

'2': 'c',

length: 3

}

console.log(Array.from(arrayLike)); // ["a","b","c"]

//字符转数组

let arr = Array.from('w3cplus.com')  //字符转数组

console.log(arr); // ["w","3","c","p","l","u","s",".","c","o","m"]

//Set数据转数组

let namesSet = new Set(['a', 'b'])  //new Set创建无重复元素数组

let arr2 = Array.from(namesSet);  //将Set结构数据转为数组

console.log(arr2); //["a","b"]

//转换Map数据

let m = new Map([[1, 2], [2, 4], [4, 8]]);

console.log(Array.from(m)); // [[1, 2], [2, 4], [4, 8]]

//接受第二个参数为map转换参数

var arr = Array.from([1, 2, 3]);  //返回一个原样的新数组

var arr1 = Array.from(arr, (x) => x * x)

console.log(arr1);    // [1, 4, 9]

var arr2 = Array.from(arr, x => x * x);

console.log(arr2);    // [1, 4, 9]

var arr3 = Array.from(arr).map(x => x * x);

console.log(arr3);    // [1, 4, 9]

//大样本生成

var arr4 = Array.from({length:5}, (v, i) => i);

console.log(arr4);  // [0, 1, 2, 3, 4]

19.includes() es6方法

参数:数值 -------- 返回值:true/false
includes()方法------是查看数组中是否存在这个元素,存在就返回true,不存在就返回false

let arr = [1,33,44,22,6,9]
let ary = arr.includes(22)
console.log(ary)   //true
 

18.js中的set与map区别?

Set 是一种叫做集合的数据结构,本身是一种构造函数,Map 是一种叫做字典的数据结构。

  • 集合 与 字典 的区别:
    • 共同点:集合、字典 可以储存不重复的值

    • 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存 

      集合(Set):操作方法:
      add(value):新增,相当于 array里的push。
      delete(value):存在即删除集合中value。
      has(value):判断集合中是否存在 value。
      clear():清空集合。
      遍历方法:遍历方法(遍历顺序为插入顺序)
      keys():返回一个包含集合中所有键的迭代器。
      values():返回一个包含集合中所有值得迭代器。
      entries():返回一个包含Set对象中所有元素得键值对迭代器。
      forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值。

       

      字典(Map):

      操作方法:
      set(key, value):向字典中添加新元素。
      get(key):通过键查找特定的数值并返回。
      has(key):判断字典中是否存在键key。
      delete(key):通过键 key 从字典中移除对应的数据。
      clear():将这个字典中的所有元素删除。
      遍历方法:
      Keys():将字典中包含的所有键名以迭代器形式返回。
      values():将字典中包含的所有数值以迭代器形式返回。
      entries():返回所有成员的迭代器。
      forEach():遍历字典的所有成员。

       

19.js如何合并数组

(1)concat()方法:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArr = arr1.concat(arr2);
console.log(mergedArr); // Output: [1, 2, 3, 4, 5, 6]
(2)使用扩展运算符(...)

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArr = [...arr1, ...arr2];
console.log(mergedArr); // Output: [1, 2, 3, 4, 5, 6]

这两种方法在合并数组方面的功能是相同的,它们都可以将两个或多个数组合并为一个数组。但是,它们的语法和用法有所不同。

concat()方法是一个数组方法,它接受一个或多个数组作为参数,并将它们合并到一个新数组中。它不会修改原始数组,而是返回一个新的合并后的数组。这个方法可以用于合并任意数量的数组。

扩展运算符(...)是ES6引入的新语法,它可以将一个数组展开成一个逗号分隔的列表。这个语法可以用于函数调用时传递参数,也可以用于数组合并。使用扩展运算符合并数组时,您需要先将要合并的数组放在一个空数组中,然后使用扩展运算符将它们展开到这个空数组中。

总的来说,使用concat()方法可以更方便地合并多个数组,而使用扩展运算符更适合合并两个数组。

20.数组的去重,交集,并集,差集

数组去重

1.使用Set对象

可以使用ES6中的Set对象来实现数组去重。将数组转换为Set对象,然后再将Set对象转换为数组即可。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = Array.from(new Set(arr));
console.log(uniqueArr); // Output: [1, 2, 3, 4, 5]

2.使用indexOf()方法

可以使用indexOf()方法来判断数组中是否存在某个元素,进而实现数组去重。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
  if (uniqueArr.indexOf(arr[i]) === -1) {
    uniqueArr.push(arr[i]);
  }
}
console.log(uniqueArr); // Output: [1, 2, 3, 4, 5]

数组取交集

可以使用filter()方法和includes()方法来实现数组取交集。

const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];
const intersectionArr = arr1.filter(item => arr2.includes(item));
console.log(intersectionArr); // Output: [3, 4, 5]

使用set求交集

let a = new Set([1, 2, 3]), b = new Set([2, 3, 4]);

//交集 var res2 = new Set([...a].filter(x => b.has(x))); console.log('交集:', res2);

//交集 Set(2) {2,3}

数组取并集

可以使用Set对象和concat()方法来实现数组取并集。

1.使用Set对象

将两个数组转换为Set对象,然后将它们合并到一个新的Set对象中,最后将新的Set对象转换为数组即可。

const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const unionSet = new Set([...arr1, ...arr2]);
const unionArr = Array.from(unionSet);
console.log(unionArr); // Output: [1, 2, 3, 4, 5]

2.使用concat()方法

将两个数组合并到一个新数组中,然后使用去重方法去重即可。

const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const unionArr = Array.from(new Set(arr1.concat(arr2)));
console.log(unionArr); // Output: [1, 2, 3, 4, 5]
 

数组取差集

//差集(a 相对于 b) var res3 = new Set([...a].filter(x => !b.has(x))); console.log('差集:', res3);

21.js中对象的操作方法有哪些?

在JavaScript中,对象是一种非常重要的数据类型,可以用来表示复杂的数据结构。下面我列出了一些常见的JavaScript对象操作方法:

1. 创建对象

在JavaScript中可以使用对象字面量或构造函数来创建一个对象。对象字面量是一种简单的方式,可以直接在代码中定义一个对象,而构造函数则是一种更灵活的方式,可以用来创建多个相似的对象。

使用对象字面量创建一个对象:

const person = {

  name: 'John',

  age: 30,

  gender: 'male'

}

使用构造函数创建一个对象:

function Person(name, age, gender) {

  this.name = name

  this.age = age

  this.gender = gender

}

const person = new Person('John', 30, 'male')

2. 访问对象属性

可以使用点号或方括号访问对象的属性。

使用点号访问对象属性:

console.log(person.name) // John

使用方括号访问对象属性:

console.log(person['name']) // John

3. 修改对象属性

可以通过点号或方括号修改对象的属性。

使用点号修改对象属性:

person.age = 40

使用方括号修改对象属性:

person['age'] = 40

4. 删除对象属性

可以使用delete关键字删除对象的属性。

delete person.age

5. 遍历对象属性

可以使用for...in循环遍历对象的属性。

for (const key in person) {

  console.log(key + ': ' + person[key])

}

6. 检查对象属性

可以使用in运算符或hasOwnProperty方法来检查对象是否具有某个属性。

使用in运算符检查对象属性:

console.log('name' in person) // true

console.log('address' in person) // false

使用hasOwnProperty方法检查对象属性:

console.log(person.hasOwnProperty('name')) // true

console.log(person.hasOwnProperty('address')) // false

当然,除了上面提到的对象操作方法,JavaScript还提供了其他一些常用的对象操作方法。下面我列出了一些常见的操作方法:

1. Object.keys(es6):返回一个包含对象所有可枚举属性名称的数组。

const person = {

  name: 'John',

  age: 30,

  gender: 'male'

}

const keys = Object.keys(person)

console.log(keys) // ['name', 'age', 'gender']

2. Object.values(es6):返回一个包含对象所有可枚举属性值的数组。

const person = {

  name: 'John',

  age: 30,

  gender: 'male'

}

const values = Object.values(person)

console.log(values) // ['John', 30, 'male']

3. Object.entries(es6):返回一个包含对象所有可枚举属性名称和属性值的二维数组。

const person = {

  name: 'John',

  age: 30,

  gender: 'male'

}

const entries = Object.entries(person)

console.log(entries) // [['name', 'John'], ['age', 30], ['gender', 'male']]

4. Object.assign(es6):将一个或多个对象的属性合并到一个目标对象中。

const person = {

  name: 'John',

  age: 30

}

const info = {

  gender: 'male'

}

const merged = Object.assign(person, info)

console.log(merged) // {name: "John", age: 30, gender: "male"}

5. Object.freeze:冻结一个对象,使其属性不可修改。

const person = {

  name: 'John',

  age: 30

}

Object.freeze(person)

person.age = 40 // 不会生效

console.log(person) // {name: "John", age: 30}

6. Object.seal:封闭一个对象,使其属性不可添加或删除,但属性值可以修改。

const person = {

  name: 'John',

  age: 30

}

Object.seal(person)

person.age = 40 // 生效

person.gender = 'male' // 不会生效

console.log(person) // {name: "John", age: 40}

7.Object.is(es6)

Object.is()方法用来判断两个值是否为同一个值,返回一个布尔类型的值。

const obj1 = {};
const obj2 = {};
console.log(Object.is(obj1, obj2)); // false

const obj3 = {};
const value1 = obj3;
const value2 = obj4;
console.log(Object.is(value1, value2)); // true
 

22.js中循环有哪些?

在JavaScript中有多种循环方式可以用来遍历数组和对象,下面是一些常见的循环方式:

1. for循环

for循环是JavaScript中最常用的循环方式之一,可以用来遍历数组和对象。for循环的缺点是需要手动控制循环变量,如果控制不好可能会导致死循环等问题。

for (let i = 0; i < arr.length; i++) {

  console.log(arr[i])

}

2. forEach循环

forEach循环是一种专门用来遍历数组的循环方式,它的优点是使用简单,不需要手动控制循环变量,缺点是无法在循环过程中使用break和continue。

arr.forEach(function(item) {

  console.log(item)

})

3. for...in循环

for...in循环可以用来遍历对象的属性,它的优点是可以遍历对象的所有属性,缺点是无法保证遍历的顺序,也会将原型链上的属性一起遍历出来。

for (const prop in obj) {

  console.log(prop + ': ' + obj[prop])

}

4. for...of循环

for...of循环是一种专门用来遍历可迭代对象(例如数组和字符串)的循环方式,它的优点是使用简单,可以使用break和continue控制循环,缺点是无法遍历对象的属性。

for (const item of arr) {

  console.log(item)

}

5. while循环

while循环是一种基本的循环方式,它的优点是使用灵活,可以根据需要手动控制循环变量,缺点是容易出现死循环等问题。

let i = 0

while (i < arr.length) {

  console.log(arr[i])

  i++

}

6.map循环

map方法是一种专门用来遍历数组并返回一个新数组的方法,它可以将原数组中的每个元素映射为一个新元素,新元素的值由回调函数返回。map方法不会改变原数组,而是返回一个新数组。

const arr = [1, 2, 3]

const newArr = arr.map(function(item) {

  return item * 2

})

console.log(newArr) // [2, 4, 6]

7.filter循环

filter方法是一种专门用来遍历数组并返回一个新数组的方法,它可以根据回调函数的返回值过滤出原数组中符合条件的元素,新数组中包含符合条件的元素。filter方法不会改变原数组,而是返回一个新数组。

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

const newArr = arr.filter(function(item) {

  return item % 2 === 0

})

console.log(newArr) // [2, 4]

8.reduce

reduce方法是一种专门用来遍历数组并返回一个单一值的方法,它可以将数组中的每个元素累加到一个初始值上,返回最终的累加结果。reduce方法不会改变原数组。

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

const sum = arr.reduce(function(prev, curr) {

  return prev + curr

}, 0)

console.log(sum) // 15

9. for await...of循环

for await...of循环是一种专门用来遍历异步可迭代对象的循环方式,它可以遍历异步可迭代对象中的每个元素并执行异步操作。for await...of循环的优点是可以处理异步操作,缺点是只能用来遍历异步可迭代对象。

async function* asyncGenerator() {

  yield 1

  yield 2

  yield 3

}

async function f() {

  for await (const x of asyncGenerator()) {

    console.log(x)

  }

}

f()

23. 请解释一下 JavaScript 的同源策略?


概念:同源策略是客户端脚本(尤其是Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
指一段脚本只能读取来自同一来源的窗口和文档的属性。

24.怎么解决跨域问题?

跨域问题指的是浏览器的同源策略限制了不同源之间的访问,通常表现为 JavaScript 代码无法从一个网站访问另一个不同域名或端口的网站的数据。解决跨域问题的方法有以下几种:

  1. JSONP:JSONP(JSON with Padding)是一种跨域访问数据的方法。其原理是利用 script 标签的跨域特性,通过在页面中添加一个 script 标签,将数据传递回调函数中,从而实现跨域访问数据。

  2. CORS:CORS(Cross-Origin Resource Sharing)是一种跨域资源共享的标准。通过在服务器端设置 Access-Control-Allow-Origin 头部信息,来允许其他域名的请求访问服务端资源。可以在服务端设置以下响应头信息:

    Access-Control-Allow-Origin: http://example.com
    Access-Control-Allow-Methods: GET, POST, PUT
    Access-Control-Allow-Headers: Content-Type
    ```
    
    
  3. 代理:通过在同域名下设置代理服务器,将跨域请求转发到目标服务器,再将目标服务器的响应返回给客户端。这种方法可以在客户端和服务端之间建立一个代理服务器来解决跨域问题。

  4. postMessage:postMessage 是 HTML5 中新增的一种跨文档通信机制,可以实现跨域通信。它可以在不同窗口、不同域名之间传递数据,通过设置正确的 origin 参数来防止跨站脚本攻击。

    postMessage 是 HTML5 中新增的一种跨文档通信机制,用于在不同窗口、不同域名之间传递数据。它可以通过设置正确的 origin 参数来防止跨站脚本攻击,是一种相对安全的跨域通信方式。

    postMessage 的使用方式如下:

    在发送消息的页面中,使用 window.postMessage 方法发送消息:

    // 发送消息
    var targetWindow = window.parent; // 目标窗口
    var message = 'Hello, world!'; // 消息内容
    var targetOrigin = 'http://example.com'; // 目标窗口的 origin
    targetWindow.postMessage(message, targetOrigin);
    

    在接收消息的页面中,使用 window.addEventListener 方法监听 message 事件:

    // 接收消息
    window.addEventListener('message', function(event) {
      if (event.origin === 'http://example.com') { // 验证消息来源
        var message = event.data; // 获取消息内容
        console.log(message); // 处理消息
      }
    });
    

    以上代码中,发送消息的页面通过 window.postMessage 方法向目标窗口发送消息,其中第一个参数是消息内容,第二个参数是目标窗口的 origin。接收消息的页面通过 window.addEventListener 方法监听 message 事件,当收到消息时,通过 event.origin 属性验证消息来源,并通过 event.data 属性获取消息内容。

    需要注意的是,为了防止跨站脚本攻击,我们需要在接收消息的页面中验证消息来源(event.origin),只有在来源正确的情况下才能处理消息。同时,为了增强安全性,可以在消息中添加一些特定的标记,来验证消息的合法性。

需要注意的是,在跨域访问数据时,需要注意安全问题,防止被恶意攻击。同时,需要根据具体情况选择合适的解决方案,比如 JSONP 只适用于 GET 请求,而 CORS 需要服务器支持等。

25.常见的http码?

HTTP 状态码是指在客户端向服务器端发送请求时,服务器端返回的状态码,用来表示服务器处理请求的结果。常见的 HTTP 状态码包括以下几种:

  1. 1xx(信息性状态码):表示服务器已经接收到请求,正在处理中。

  2. 2xx(成功状态码):表示服务器已经成功处理请求,并返回相应的结果。

    • 200 OK:表示请求已成功,返回的信息可能是页面内容或者其他资源。
    • 201 Created:表示请求已被成功处理,并创建了新的资源。
    • 204 No Content:表示请求已成功,但没有返回任何内容。
  3. 3xx(重定向状态码):表示请求需要进一步处理,客户端需要进行重定向。

    • 301 Moved Permanently:表示请求的资源已经永久移动到新的 URL,客户端需要重新发起请求。
    • 302 Found:表示请求的资源已经临时移动到新的 URL,客户端需要重新发起请求。
    • 304 Not Modified:表示客户端缓存的资源未被修改,可以直接使用缓存的资源,无需重新请求。
  4. 4xx(客户端错误状态码):表示客户端发送的请求有误,服务器无法处理。

    • 400 Bad Request:表示客户端请求有误,服务器无法处理。
    • 401 Unauthorized:表示客户端未经授权,无法访问请求的资源。
    • 403 Forbidden:表示客户端没有权限访问请求的资源。
    • 404 Not Found:表示请求的资源不存在,服务器无法找到请求的资源。
  5. 5xx(服务器错误状态码):表示服务器在处理请求时出现了错误。

    • 500 Internal Server Error:表示服务器在处理请求时发生了错误。
    • 502 Bad Gateway:表示服务器作为网关或代理,从上游服务器接收到的响应无效。
    • 503 Service Unavailable:表示服务器当前无法处理请求,通常是因为服务器过载或正在维护。

需要注意的是,HTTP 状态码只是为了让客户端和服务器端之间能够进行有效的通信,实际上并不代表请求是否成功或失败,具体的处理结果需要根据实际情况进行判断。

26.常用的 HTTP 请求方法?

  • GET:用于获取资源,通常不会对服务器上的资源进行修改;
  • POST:用于向服务器提交数据,通常会对服务器上的资源进行修改;后一个请求不会把第一个请求覆盖掉。(所以Post用来增资源)
  • PUT:用于向服务器更新资源,通常会对服务器上的资源进行修改;如果两个请求相同,后一个请求会把第一个请求覆盖掉。(所以PUT用来改资源)
  • DELETE:用于删除服务器上的资源;
  • HEAD:与 GET 方法类似,但只返回响应头部信息,不返回响应体;
  • OPTIONS:用于查询服务器支持的 HTTP 方法;
  • 其中,GETPOST 是最常用的两种请求方法。GET 请求通常用于获取数据,例如从服务器获取网页、图片、视频等资源;而 POST 请求通常用于提交表单数据、上传文件等操作。

    需要注意的是,HTTP 请求方法并不限于上述几种,HTTP 协议规定了多种请求方法,不同的请求方法具有不同的语义和用途。例如,PATCH 请求用于对服务器上的资源进行局部修改,TRACE 请求用于回显服务器接收到的请求信息等。                       

27.http和https区别?

HTTP(HyperText Transfer Protocol)和 HTTPS(HyperText Transfer Protocol Secure)都是用于在客户端和服务器之间传输数据的协议,它们之间的主要区别在于安全性和加密方式。

HTTP 是一种明文传输协议,数据在传输过程中不加密,容易被窃听和篡改。HTTPS 在 HTTP 的基础上增加了 SSL/TLS 加密,可以保证数据在传输过程中的机密性、完整性和可信性,能够有效防止窃听和篡改。

具体来说,HTTPS 在建立连接时会使用 SSL/TLS 协议进行加密,包括以下几个步骤:

  1. 客户端向服务器发送连接请求。

  2. 服务器将自己的公钥发送给客户端。

  3. 客户端使用服务器的公钥对一个随机生成的对称密钥进行加密,并将加密后的密钥发送给服务器。

  4. 服务器使用自己的私钥对客户端发送的加密后的密钥进行解密,并使用该对称密钥对后续的数据进行加密。

  5. 客户端使用对称密钥对数据进行加密,并将加密后的数据发送给服务器。

  6. 服务器使用对称密钥对数据进行解密,并进行后续的处理。

由于 HTTPS 使用了 SSL/TLS 加密,可以有效保护数据在传输过程中的安全性和完整性,因此在涉及到用户隐私和敏感信息的场景中广泛应用,例如在线支付、电子商务等。

28.从输入url到显示页面,都经历了什么?

从输入 URL 到显示页面,整个过程可以分为以下几个步骤:

  1. DNS 解析:将域名解析为 IP 地址。浏览器会先检查本地缓存是否有对应的 IP 地址,如果没有则向本地 DNS 服务器发起请求,如果还没有则向根域名服务器发起请求,逐级向下直到找到对应的 IP 地址。

  2. TCP 连接:浏览器与服务器建立 TCP 连接。TCP 三次握手的过程中,客户端向服务器发送 SYN 报文,服务器回复 SYN+ACK 报文,客户端再回复 ACK 报文,完成连接建立。

  3. 发送 HTTP 请求:浏览器向服务器发送 HTTP 请求。请求报文包括请求行、请求头、请求体等信息。

  4. 服务器处理请求并返回响应:服务器接收到请求后,根据请求报文中的信息进行处理,并返回响应报文。响应报文包括状态行、响应头、响应体等信息。

  5. 接收响应并渲染页面:浏览器接收到响应报文后,根据响应报文中的信息进行处理,并开始渲染页面。渲染过程包括解析 HTML、构建 DOM 树、计算 CSS 样式、构建渲染树、布局和绘制等步骤。

  6. 断开 TCP 连接:浏览器和服务器之间的 TCP 连接在数据传输完毕后会被断开,四次挥手的过程中,客户端向服务器发送 FIN 报文,服务器回复 ACK 报文,服务器向客户端发送 FIN 报文,客户端回复 ACK 报文,完成连接断开。

以上是整个过程的基本流程,其中还包括一些细节和优化,例如浏览器缓存、HTTP 2.0 多路复用等。

ACK 报文、FIN 报文和 SYN 报文是 TCP 协议中的三种控制报文段,用于控制 TCP 连接的建立、维护和关闭。

  • SYN 报文:用于建立 TCP 连接。当客户端向服务器发起连接请求时,客户端会发送一个 SYN 报文,其中包含一个随机的序列号。服务器收到 SYN 报文后,会回复一个 SYN+ACK 报文,其中包含确认号和另一个随机序列号。客户端收到 SYN+ACK 报文后,会回复一个 ACK 报文,其中包含确认号(等于服务器的初始序列号+1),表示连接已经建立。

  • ACK 报文:用于确认收到的数据。当一方收到另一方发送的数据时,会回复一个 ACK 报文,其中包含确认号,表示已经成功接收到了指定序列号之前的所有数据。

  • FIN 报文:用于关闭 TCP 连接。当一方想要关闭连接时,会发送一个 FIN 报文,表示已经没有数据要发送了。另一方收到 FIN 报文后,会回复一个 ACK 报文,表示已经接收到了 FIN 报文。当另一方也没有数据要发送时,会发送一个 FIN 报文,表示连接已经关闭。最后一方收到 FIN 报文后,也会回复一个 ACK 报文,表示连接已经关闭。

以上三种报文是 TCP 协议中最常用的控制报文,用于建立、维护和关闭 TCP 连接。

29. ajax过程?


(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
(3)设置响应HTTP请求状态变化的函数.
(4)发送HTTP请求.
(5)获取异步调用返回的数据.
(6)使用JavaScript和DOM实现局部刷新.

30. 事件队列(宏任务微任务)eventloop机制

JavaScript 中的事件循环(Event Loop)是一种机制,用于协调异步代码的执行顺序。事件循环的基本原理是,将所有的任务分为两类:同步任务和异步任务。同步任务会在主线程上按照顺序执行,而异步任务会被放入任务队列中,等待主线程空闲时被执行。

事件循环由以下几个部分组成:

  1. 执行栈(Execution Context Stack):用于存储 JavaScript 代码的执行环境(Execution Context),包括当前正在执行的函数、变量、参数等信息。执行栈遵循先进后出(Last-In-First-Out)的原则,即最后进入栈的函数最先执行完毕。

  2. 任务队列(Task Queue):用于存储异步任务(如定时器、事件回调等)的回调函数。当异步任务完成时,它们会被放入任务队列中等待执行。

  3. 事件循环线程(Event Loop Thread):用于监听执行栈和任务队列中的任务,并将任务放入执行栈中执行。事件循环线程不断地从任务队列中取出任务,并将它们放入执行栈中执行,直到所有的任务都被执行完毕。

具体来说,事件循环的执行过程如下:

  1. 执行栈中的同步任务按照顺序执行。

  2. 遇到异步任务时,将其回调函数放入任务队列中等待执行。

  3. 当执行栈中的同步任务执行完毕时,事件循环线程会从任务队列中取出一个任务,并将其回调函数放入执行栈中执行。

  4. 重复步骤 1-3,直到任务队列中的所有任务都被执行完毕。

需要注意的是,任务队列中的任务分为宏任务(Macro Task)和微任务(Micro Task)两种类型。在每次执行完一个宏任务后,事件循环会先执行所有的微任务,然后再执行下一个宏任务。微任务包括 Promise 回调函数、MutationObserver 回调函数等,而宏任务包括 setTimeout、setInterval、I/O 操作等。

下面是一个简单的示例,演示了事件循环的执行过程:

console.log('1');

setTimeout(function() {
  console.log('2');
}, 0);

Promise.resolve().then(function() {
  console.log('3');
});

console.log('4');
这个示例中,首先输出 14,然后将一个定时器回调函数和一个 Promise 回调函数放入任务队列中。接着,事件循环线程会执行所有的微任务,即输出 3。最后,事件循环线程会从任务队列中取出定时器回调函数,并将其放入执行栈中执行,输出 2

31. Promise

Promise是一种用于异步编程的JavaScript对象,它可以让我们更方便地处理异步操作的结果。

Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),初始状态为pending。当Promise状态从pending转变为fulfilled或rejected时,就称为Promise已“settled”。

Promise有以下几个方法:

  1. Promise.resolve():返回一个已成功的Promise对象,可以将任意值转换为Promise对象。

  2. Promise.reject():返回一个已失败的Promise对象,可以将任意值转换为失败的Promise对象。

  3. Promise.prototype.then():用于注册成功回调函数,接收两个参数,第一个是成功回调函数,第二个是失败回调函数。当Promise状态为fulfilled时,会调用成功回调函数;当Promise状态为rejected时,会调用失败回调函数。

  4. Promise.prototype.catch():用于注册失败回调函数,接收一个参数,即失败回调函数。当Promise状态为rejected时,会调用失败回调函数。

  5. Promise.prototype.finally():用于注册不管Promise状态如何都会执行的回调函数,接收一个参数,即回调函数。无论Promise状态为fulfilled还是rejected,都会执行该回调函数。

具体用法可以参考以下代码示例:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = {
        name: "John",
        age: 30
      };
      resolve(data);
    }, 2000);
  });
}

fetchData()
  .then(data => {
    console.log(data); // { name: "John", age: 30 }
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    console.log("Fetching data completed.");
  });
以上示例中,我们使用Promise封装了一个异步操作fetchData(),并通过then()catch()finally()方法分别注册了成功、失败和不管状态如何都会执行的回调函数。

32. async/await 


async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。是Generator函数的语法糖,并对Generator函数进行了改进。
改进:

内置执行器,无需手动执行 next() 方法。
更好的语义
更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
返回值是 Promise,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
async 隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。                         
 

Vue篇

1. 什么是MVVM?

MVVM 是一种软件架构模式,它将应用程序分为三个部分:模型(Model)、视图(View)和视图模型(ViewModel)。MVVM 模式的主要目的是实现 UI 逻辑和业务逻辑的分离,从而提高应用程序的可维护性、可测试性和可扩展性。

具体来说,MVVM 模式中的三个部分分别承担以下角色:

  • 模型(Model):表示应用程序的数据和业务逻辑。模型通常包含数据结构、数据库操作、网络请求等功能。

  • 视图(View):表示应用程序的用户界面。视图通常由 XAML、HTML 或其他 UI 技术创建,并与视图模型进行绑定。

  • 视图模型(ViewModel):表示视图的状态和行为。视图模型通常包含用户界面的逻辑、命令、数据绑定等功能,并且与模型进行交互。

在 MVVM 模式中,视图和视图模型之间通过数据绑定进行通信,视图模型通过命令将用户输入传递给模型进行处理,并将处理结果返回给视图进行显示。这种分离使得 UI 逻辑和业务逻辑可以独立开发和测试,同时也提高了代码的可重用性和可维护性。

总之,MVVM 是一种优秀的软件架构模式,可以帮助开发人员更好地组织应用程序的代码,并提高应用程序的质量和可维护性。

2. 为什么data是一个函数

  • 为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
  • 因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,
    如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
     

3、Vue组件通讯有哪些方式?

(1)props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref 与 $parent / $children适用 父子组件通信

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
(3)EventBus ($emit / $on)适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)$attrs/$listeners适用于 隔代组件通信

$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
(5)provide / inject适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
 

4. Vue 的父组件和子组件生命周期钩子函数执行顺序?

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

加载渲染过程 :
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程 :
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程 :
父 beforeUpdate -> 父 updated
销毁过程 :
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

5. 父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {
  this.$emit("mounted");
}
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},

//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    

// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听

6.v-model 的原理?

要在自定义组件中实现v-model,你需要在组件的props中声明一个value属性和一个名为input的事件。value属性将用于接收父组件传递的值,而input事件将用于向父组件发出更新的信号。

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      :value="value"
      @input="$emit('input', $event.target.value)"
    >
  `
})

在上面的示例中,我们在props中声明了一个名为value的属性,并将其绑定到input元素的值上。然后,我们使用@input指令绑定一个事件监听器,该事件监听器会在输入框的值发生变化时触发,并使用$emit方法向父组件发出名为input的事件,并将当前输入框的值作为参数传递给父组件。

现在,你可以在父组件中使用v-model指令来绑定自定义组件的值了。例如:

<custom-input v-model="message"></custom-input>
在上面的示例中,我们使用v-model指令将message变量与自定义组件绑定起来。这意味着当自定义组件的值发生变化时,message变量也会相应地更新。

7.vue中导航守卫?

vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
每个守卫接受三个参数:
(1)to:即将要进入的目标路由对象
(2)from:当前导航正要离开的路由
(3) next:一定要调用该方法来resolve这个钩子。执行效果依赖next方法的调用参数。如果next函数没有执行或是传入了false等值,这个跳转就会被终止掉。

2.全局守卫
只要触发了路由就会触发路由前置和后置守卫
(1)全局前置守卫
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫resolve完之前一直处于等待中。只有等所有守卫resolve之前一直处于等待中。

用途:可以用来判断进入的路由是否想让一开始就进入的,如果不是可以让它跳转过去。

// 可以使用 router.beforeEach 注册一个全局前置守卫
//通过判断path,来进行操作.如果进入的路由不是foo1,那么就让他进去foo2中
const router = new VueRouter({ routes:[{name:"demo1",path:"/demo1",component:  ,}]... })

router.beforeEach((to, from, next) => {
  // ...
    //next()
    if(to.path !== '/foo1'){
    //next({name:'/foo2'})
    next({path:'/foo2'})
}
next()
})

(2)全局后置守卫

后置守卫就没有next了,只有to和from。
用途:可以设置在进入路由后网页标题显示为对应路由里的元数据中的title
在routers中添加meta:{title:‘音乐排行榜’}
使用后置守卫:router.afterEach((t0,from)=>{
document.title = to.meta.title
})

3.路由独享守卫(beforeEnter)

写在routes中的,每个路由独有的,只在当前路由中生效。

// 可以在路由配置上直接定义 beforeEnter 守卫
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...next() //当不给next的时候,就访问不了这个路由。
          const isLogin = false  //当要进入这个页面的时候要进行判断,如果登录了就让进入,如果没有登录就进入到登录页面中。
          if(!isLogin){
              next()
          }else {
              next('login')
          }
      }
    }
  ]
})
4.组件内的守卫(3个)
直接写在对应组件页面中。
beforeRouterEnter:进入该组件的对应路由后触发
beforeRouterUpdate:当同一个组件,path参数不同时,进行切换的时候触发。比如这种:path:/music/:id
beforeRouterLeave:要离开该组件的对应路由时触发。
(1)beforeRouteEnter
在这里不能使用this,这时实例还没有被创建
如果要使用this。用下面这种办法:在next中通过vm来代替this
beforeRouterEnter(to,from,next) {
    next( vm =>{
          console.log(vm)   //相当于this
  })
}
(2)beforeRouteUpdate
在当前路由改变,但是只有在该组件被复用时调用。
举例来说:对于一个带有动态参数的路径/foo/:id 在/foo/1与/foo/2之间进行切换跳转的时候会复用foo组件,所以才会触发这个钩子。
(3)beforeRouteLeave
案例:当所在组件的路由要离开时,给个弹窗,是否退出,是就退出,next中值为false则不退出。
beforeRouteLeave(to,from,next){
    const answer = window.confirm("你确定要离开吗")
    if(answer){
        next()
    }else {
        next(false)
    }
}
 

8.v-for的key的作用

在使用v-for遍历数据渲染列表时,一般会给元素添加一个key属性,这个key的作用是什么呢?

官方的解释:

key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes;如果不使用key,vue会使用一种最大限度减少动态元素并且尽可能尝试就地修改/复用相同类型元素的算法;而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素。


9.Vuex

一.Vuex是什么?为什么要用它?

 vuex官方解释

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

核心概念

vuex中一共有五个状态 State  Getter  Mutation   Action   Module  下面进行详细讲解

1.State

提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data

 在vuex中state中定义数据,可以在任何组件中进行调用

import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  //数据,相当于data
  state: {
    name:"张三",
    age:12,
    count:0
  },
})

 调用:

方法一:

在标签中直接使用

 方法二:

this.$store.state.全局数据名称

方法三:

从vuex中按需导入mapstate函数

import { mapState } from "vuex";

注意:当前组件需要的全局数据,映射为当前组件computed属性


 

5.2 Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

 在vuex中定义:

其中参数state参数是必须的,也可以自己传递一个参数,如下代码,进行计数器的加减操作,加法操作时可以根据所传递参数大小进行相加,减法操作没有传参每次减一

在组件中使用:

 定义两个按钮进行加减操作

 方法一:

注意:使用commit触发Mutation操作

methods:{
//加法
btn(){
this.$store.commit("addcount",10)     //每次加十
}
//减法
btn1(){
this.$store.commit("reduce") 
}
}
方法二:

使用辅助函数进行操作,具体方法同上

 5.3  Action ——进行异步操作
Action和Mutation相似,一般不用Mutation 异步操作,若要进行异步操作,使用Action

原因:为了方便devtools打个快照存下来,方便管理维护。所以说这个只是规范,而不是逻辑的不允许,只是为了让这个工具能够追踪数据变化而已

在vuex中定义:

将上面的减法操作改为异步操作

 在组件中使用:

方法一:

直接使用  dispatch触发Action函数

this.$store.dispatch("asynAdd")
方法二:

使用辅助函数

 5.4 Getter
类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据

 具体操作类似于前几种,这里不做具体说明

5.5  Modules
当遇见大型项目时,数据量大,store就会显得很臃肿

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

 默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

10.SPA单页面应用(优化首屏加载)

一、概念
SPA(single-page application):通过动态重写当前页面来与用户交互
代码通过单个页面的加载而检索,或者动态装载适当的资源并添加到页面

JS框架如 react, vue, angular, ember 都属于SPA

二、SPA和MPA的区别
MPA(MultiPage-page application):每个页面都是独立的( 都需要重新加载html、css、js文件,公共文件则根据需求按需加载 )


三、 优缺点
优点:
1)具有桌面应用的即时性、网站的可移植性和可访问性
2)用户体验好、快,内容的改变不需要重新加载整个页面
3)良好的前后端分离,分工更明确
缺点:
1)不利于搜索引擎的抓取
2)首次渲染速度相对较慢

四、实现
原理:
1)监听地址栏中hash变化驱动界面变化
2)用pushstate记录浏览器的历史,驱动界面发送变化
hash 模式:监听url中的hash来进行路由跳转
history模式:history 模式核心借用 HTML5 history api

history.pushState 浏览器历史纪录添加记录
history.replaceState修改浏览器历史纪录中当前纪录
history.popState 当 history 发生变化时触发
五、给SPA做SEO
SSR服务端渲染
将组件或页面通过服务器生成html,再返回给浏览器,如nuxt.js
静态化
目前主流的静态化主要有两种:
(1)一种是通过程序将动态页面抓取并保存为静态页面,这样的页面的实际存在于服务器的硬盘中
(2)另外一种是通过WEB服务器的 URL Rewrite的方式,它的原理是通过web服务器内部模块按一定规则将外部的URL请求转化为内部的文件地址,一句话来说就是把外部请求的静态地址转化为实际的动态页面地址,而静态页面实际是不存在的。
这两种方法都达到了实现URL静态化的效果
使用Phantomjs针对爬虫处理
原理是通过Nginx配置,判断访问来源是否为爬虫,如果是则搜索引擎的爬虫请求会转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫。
六、首屏加载
首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

1️⃣ 加载慢的原因
1)网络延时
2)资源文件体积过大
3)资源重复发送请求
4)加载脚本的时候,渲染内容堵塞了

2️⃣ 解决方案
总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化
资源加载优化:
减小资源大小(压缩,gzip);减少http请求(缓存);提高响应速度(CDN);优化资源加载(按需加载,懒加载)
页面渲染优化:
优化html代码(减少dom,js外链底部,css外链顶部);优化js,css代码(减少重绘,降低选择器复杂性);优化动画效果(用transform/opacity)

1)减小入口文件体积
路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由
在vue-router配置路由的时候,采用动态加载路由的形式

routes:[ 
    path: 'Blogs',
    name: 'ShowBlogs',
    component: () => import('./components/ShowBlogs.vue')
]
1
2
3
4
5
以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件

2)静态资源本地缓存
利用localStorage

3)UI框架按需加载

import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)
1
2
3
4
4)图片资源的压缩
对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力。

5)组件重复打包
在webpack的config文件中,修改CommonsChunkPlugin的配置

minChunks: 3 //表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件
1
6)开启GZip压缩
用gzip做一下压缩 安装compression-webpack-plugin

7)使用SSR
SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器
从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值