每日一点面试题

说一下对cookie和Storage(localStorage和sessionStorage) 的认识和区别?

将用户信息保存在浏览器中。

早期本地储存采用的是cookie的方式,但是cookie大小有限,并且前端操作不方便,所以H5给了两个新的储存方法

  • localStorage :永久性本地存储,只要不手动删除或者代码清除,一直保存在浏览器中。
  • sessionStorage : 会话级本地存储,只有在当前窗口下才生效,窗口关闭数据清空。

一般来说用于储存后台返回的token、用户信息等常用数据。

本地存储的区别

区别cookielocalStorage(优选)sessionStorage
数据有效期默认窗口关闭前有效(可以手动设置有效期)永久有效窗口关闭前有效
存储大小4kb至少5mb至少5mb
作用范围同源同窗口同源窗口共享同源同窗口
与服务器通信每次都会携带cookie不会自动携带不会自动携带

如何实现水平、垂直居中?

弹性盒子方法

        display: flex;
	justify-content: center;
	align-items: center;

定位方法

        position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	margin: auto;

盒子模型(怪异模型)

标准盒模型

image-20210824105428660

上面就是一个标准盒模型:

  1. content:代表内容区域,存放内容,文本或者里面图片等等
  2. padding:内边距,盒子内部的空间,内容与边框之间的间距,相当于快递中的泡沫
  3. border:盒子的边框,四周边框可以分别设置。
  4. margin:代表外边距,盒子和盒子之间的间距(父子关系,兄弟关系)

如何怪异盒子(IE盒模型)

image-20210824152852636

width和height包含了content和padding和border,目前主流的浏览器默认都是标准盒模型

  • 可以通过box-sizing来转换盒模型
    • content-box:标准盒模型
    • border-box:怪异盒模型

怪异盒子真正的大小的计算:

  • 宽度= width(包含了content+padding+border)
  • 高度= height(包含了content+padding+border)

怪异盒子所占空间的大小的计算:

  • 宽度=width(包含了content+padding+border)+ margin * 2(左右)
  • 高度 =height(包含了content+padding+border)+ margin * 2(上下)

实现图文混排

float

如何判断数据类型

1、使用 typeof

let obj={
            name:'dawn',
            age:21
        }
        let fn=function() {
            console.log('我是 function 类型');
        }
        console.log(typeof 1);       //number
        console.log(typeof 'abc');   //string
        console.log(typeof true);    //boolean
        console.log(typeof undefined);  //undefined 
        console.log(typeof fn);      //function
        console.log(typeof (new Date) );  //object
        console.log(typeof null);     //object
        console.log(typeof [1,2,3]);  //object
        console.log(typeof obj);      //object

由结果可知typeof可以测试出numberstringbooleanundefinedfunction

2、使用 instanceof

obj instanceof Object ,可以左边放你要判断的内容,右边放类型来进行JS类型判断,只能用来判断复杂数据类型,因为instanceof 是用于检测构造函数(右边)的 prototype 属性是否出现在某个实例对象(左边)的原型链上。

let arr=[1,2,3,4,5,6,7]
        let obj={
            name:'dawn',
            age:21
        }
        let fn=function() {
            console.log('我是 function 类型');
        }
 
       console.log(arr instanceof Array);  //true
       console.log(obj instanceof Object);  //true
       console.log(fn instanceof Function);  //true
       console.log((new Date) instanceof Date);  //true

3、使用Object.prototype.toString.call

let obj = {
            name: 'dawn',
            age: 21
        }
        let fn = function () {
            console.log('我是 function 类型');
        }
        
        console.log(Object.prototype.toString.call(1));        // [object Number]
        console.log(Object.prototype.toString.call('Hello tomorrow')); // [object String ]
        console.log(Object.prototype.toString.call(true));     // [object Boolean]
        console.log(Object.prototype.toString.call(undefined));  // [object Undefined]
        console.log(Object.prototype.toString.call(fn));   // [object Function]
        console.log(Object.prototype.toString.call(new Date));  // [object Date]
        console.log(Object.prototype.toString.call(null));   // [object Null]
        console.log(Object.prototype.toString.call([1, 2, 3]));  // [object Array]
        console.log(Object.prototype.toString.call(obj));       // [object Object]

在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstructorName] 格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。
但是它不能检测非原生构造函数的构造函数名。

4、使用constructor

let arr = [1, 2, 3, 4, 5, 6, 7]
        let obj = {
            name: 'dawn',
            age: 21
        }
        let fn = function () {
            console.log('我是 function 类型');
        }
 
        console.log((9).constructor === Number);  //true
        console.log('hello'.constructor === String);  //true
        console.log(true.constructor === Boolean);  //true
        console.log(fn.constructor === Function);  //true
        console.log((new Date).constructor === Date);  //true
        console.log(obj.constructor === Object);  //true
        console.log([1, 2, 3].constructor === Array);  //true

constructor不能判断undefined和null,并且使用它是不安全的,因为contructor的指向是可以改变的

在数组上新增一个方法,实现复制。期望:[1,2,3].copy() //输出 [1,2,3,1,2,3]

  Array.prototype.copy = function () {
        arr = this;//this指的是数组[1, 2, 3]
        arr.forEach((value) => {
            arr.push(value);
        });
        return arr
    };
    console.log([2, 3, 4, 5].copy());

一个页面上有大量的图片(大型电商网站),加载很慢,你有哪些方法优化这些图片的加载,给用户更好的体验。

图片懒加载,在页面上的未可视区域可以添加一个滚动条事件,判断图片位置与浏

览器顶端的距离与页面的距离,如果前者小于后者,优先加载

如果图片为 css 图片,可以使用 CSSsprite(精灵图),SVGsprite(精灵图),

Iconfont(字体图标)(精灵图,小图标…)

如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的

缩略图,以提高用户体验

如果图片展示区域小于图片的真实大小,则应该在服务器端根据业务需要先行进行

图片压缩,图片压缩后大小与展示一致

export和export default的区别

export 和 export default 都可用于导出常量、函数、文件、模块等 ,
你可以在其它文件或模块中通过 import 将其导入,以便能够对其进行使用
在一个文件或模块中,export、import可以有多个,export default仅有一个
通过export方式导出,在导入时要加{ },且不能自定义名字,export default不用加{ },且可以自定义名字

js垃圾回收机制

JS的垃圾回收机制是为了防止内存泄漏(已经不需要的某一块内存还一直存在着),垃圾回收机制就是不停歇的寻找这些不再使用的变量,并且释放掉它所指向的内存。

在JS中,JS的执行环境会负责管理代码执行过程中使用的内存。

垃圾回收机制

标记清除法

核心思想就是将整个垃圾回收操作分为两个阶段:

遍历所有的对象找到活动对象,进行标记的操作
遍历所有的对象,找到那些没有标记的对象进行清除。(注意在第二阶段中也会把第一阶段涉及的标志给抹掉,便于GC下次能够正常的工作)
通过两次的遍历行为把我们当前的垃圾空间进行回收,最终交给我们的空闲列表进行维护。

总结
核心思想:分标记和清除两个阶段:

遍历所有的对象找活动对象做标记
遍历所有的对象清除没有标记的对象
回收相应空间
优点
可以回收循环引用的对象空间。相对于引用计数算法来说:解决对象循环引用的不能回收问题。

缺点
容易产生碎片化空间,浪费空间、不能立即回收垃圾对象。

空间碎片化:所谓空间碎片化就是由于当前所回收的垃圾对象,在地址上面是不连续的,由于这种不连续造成了我们在回收之后分散在各个角落,造成后续使用的问题

引用计数

内部通过引用计数器,来维护当前对象的引用数,从而判断该对象的引用数是否为0,来决定它是否是一个垃圾对象。如果引用数值为0,GC就开始工作,将其所在的内存空间进行回收释放和再使用

引用计数器
当某一个对象的引用关系发生改变时,引用计数器就会自动的修改这个对象所对应的引用数值(比如我们代码中有一个对象空间,一个变量引用了它,那么这个对象空间的引用数值加1) 引用数值为0的时候GC就开始工作,将当前的对象空间回收

优点
可以即时回收垃圾对象、减少程序卡顿时间。

发现垃圾立即回收(因为根据当前的引用数值是否为0判断他是否是一个垃圾,如果是垃圾就回收内存空间,释放内存)
最大限度的减少程序暂停(应用程序在执行的过程中必定会对内存进行消耗,而当前的执行平台内存空间是有上限的,所以内存肯定会有占满的时候。由于引用计数算法时刻监控着那些引用数值为0的对象,当内存爆满的时候会去找那些引用数值为0的对象释放其内存,这个也就保证了当前的内存空间不会有占满的时候)
缺点
无法回收循环引用的对象、资源消耗较大

无法回收循环引用的对象
时间开销大(当前的引用计数需要去维护一个数值的变化,时刻监控当前引用数值是否修改,修改需要时间)

原型、原型链

原型

JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身

原型链

原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法

在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法

js对数组有哪些操作方法(增删改查)及其他最少说7个

下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响

  • push()
  • unshift()
  • splice()
  • concat()

下面三种都会影响原数组,最后一项不影响原数组:

  • pop()
  • shift()
  • splice()
  • slice()

即修改原来数组的内容,常用splice

  • splice()

即查找元素,返回元素坐标或者元素值

  • indexOf()
  • includes()
  • find()

排序方法

  • reverse()
  • sort()

转换方法

  • join()

迭代方法

常用来迭代数组的方法(都不改变原数组)有如下:

  • some()
  • every()
  • forEach()
  • filter()
  • map()

vue如何实现双向数据绑定?

vue实现数据双向绑定的主要是:

采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应监听回调。

当创建Vue实例时,vue会遍历data选项的属性,利用Objet.defineProperty为属性添加getter和setter对数据的读取进行劫持(getter用来依赖收集,setter用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

每个组件实例会有对应的watcher实例,在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有computed watcher,user watcher实例),之后依赖项被该改动时,setter方法会通知依赖与此data的watcher实例重新计算(派发更新),从而使它关联的组件重新渲染。

为什么在Vue3.0采用了Proxy,抛弃了Object.defineProperty

Object.defineProperty本身有一定的监控到数组下标变化的能力,但是在Vue中,从性能/体验的性价比考虑,弃用了这个特性(Vue为什么不能检测数组的变动)。为了解决这个问题,经过Vue内部处理后可以使用以下几种方法来监听数组

push()、pop()、shift()、unshift()、splice()、sort()、reverse()

由于只针对了以上7种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。

Object.defineProperty只能劫持对象的属性,因此需要对每个对象的每个属性进行遍历。Vue 2.x是通过递归+遍历data对象来实现对数据的监听的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。Proxy可以劫持整个对象,并返回一个新的对象。Proxy不仅可以代理对象,代理数组。还可以代理动态增加的属性。

Proxy相较于Object.defineProperty的优势

1.直接监听对象而非属性

2.直接监听数组的变化

3.拦截方式较多

4.Proxy返回一个新对象,可以只操作新对象达到目的,而Object.defineProperty只能遍历对象属性直接修改(需要用深拷贝进行修改)

5.Proxy作为新标准将受到浏览器厂商重点持续的性能优化

6.Proxy不能用polyfill来兼容,polyfill主要抚平不同浏览器之间对js实现的差异。

微信小程序获取用户信息

wx.getUserInfo({})

HTTP状态码200/302/401/404/500分别代表什么意思

2xx (成功)

200 – 服务器成功返回网页

3xx (重定向)

302(临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。

4xx(请求错误)

401(未授权) 请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。

404 – 请求的网页不存在

5xx(服务器错误)

500(服务器内部错误) 服务器遇到错误,无法完成请求。

定时器 settimeout和setinterval的区别,如果用settimeout实现每隔一定的时间就执行一次,怎么实现

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式, 而setInterval()则是在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。

也就是说setTimeout()只执行一次,setInterval()可以执行多次。

    function timer() {
        setTimeout(function () {
            console.log(111);
            timer()
        }, 1000)
    }
    timer()

如何改变this指向

callapplybind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向

apply

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入

改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

call

call方法的第一个参数也是this的指向,后面传入的是一个参数列表

apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

bind

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)

改变this指向后不会立即执行,而是返回一个永久改变this指向的函数

封装时间格式转换函数formatDate(时间,格式(Y-M-D h:m:s))

function formatDate(value = Date.now(), format = "Y-M-D h:m:s") {
    //函数传入字符串 给字符串前面加0 截取后两位 例如传入 1 返回 01 传入 11 返回 11
    const formatNumber = n => `0${n}`.slice(-2);
    //将字符串转为时间对象
    const date = new Date(value);
    const formatList = ["Y", "M", "D", "h", "m", "s"];
    const resultList = [];
    //获取年
    resultList.push(date.getFullYear().toString());
    // 获取月
    resultList.push(formatNumber(date.getMonth() + 1));
    //获取日
    resultList.push(formatNumber(date.getDate()));
    //获取小时
    resultList.push(formatNumber(date.getHours()));
    //获取分钟
    resultList.push(formatNumber(date.getMinutes()));
    //获取秒
    resultList.push(formatNumber(date.getSeconds()));

    //将字符串 format 中对应的字母(formatList[i]) 替换为  格式化的时间 resultList[i]
    for (let i = 0; i < resultList.length; i++) {
        format = format.replace(formatList[i], resultList[i]);
    }
    //返回时间
    return format;
}

浅拷贝与深拷贝有何区别?如何实现?

浅拷贝

浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

JavaScript中,存在浅拷贝的现象有:
  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制

深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:
  • _.cloneDeep()
  • jQuery.extend()
  • JSON.stringify()
  • 手写循环递归

同步和异步的区别,优缺点?

  • 同步
    • 代码按照编写好的顺序依次执行的过程被称为同步执行,执行的代码被称为同步代码。
    • 优点:
      • 代码逻辑清晰,数据加载明确。
    • 缺点:
      • 执行效率低,系统资源利用率不高。
  • 异步
    • 代码未按照编写顺序依次执行的过程被称为异步执行,被执行的代码被称为异步代码。
    • 优点:
      • 执行效率高,系统资源利用高,
    • 缺点
      • 代码执行逻辑不够清晰,数据加载相对不够明确。
    • 常见的:
      • 定时器
      • ajax async属性为true时的请求。

请说出以下结果输出什么?为什么?

for(var i = 0; i < 5; i++) {

  setTimeout(function(){

      console.log(i)

  }, 0)

}

输出结果为: 5,5,5,5,5

原因:

1.var不具有块级作用域,所以i为全局变量,

2.setTimeout为异步方法,需要等同步方法执行完毕后执行console.log(i)操作,此时i经过循环++ ,i=5同步代码执行完成后执行异步方法 console.log(i) 的值为 5

var i = 0
{ 
     setTimeout() //加入任务队列
    i++  //1
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //2
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //3
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //4
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //5
    //不满足条件 跳出循环
}
//同步执行结束 执行异步方法
{
    console.log(i)  //5
}
{
    console.log(i) //5
}
{
    console.log(i) //5
}
{
    console.log(i) //5
}
{
    console.log(i) //5
}

等同步方法执行完毕后执行console.log(i)操作,此时i经过循环++ ,i=5同步代码执行完成后执行异步方法 console.log(i) 的值为 5

var i = 0
{ 
     setTimeout() //加入任务队列
    i++  //1
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //2
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //3
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //4
}
//i<5继续循环
{
    setTimeout() //加入任务队列
    i++ //5
    //不满足条件 跳出循环
}
//同步执行结束 执行异步方法
{
    console.log(i)  //5
}
{
    console.log(i) //5
}
{
    console.log(i) //5
}
{
    console.log(i) //5
}
{
    console.log(i) //5
}

如何用JS实现圆形区域点击事件。

1、map+area 或者 svg

2、border-radius

3、纯 js 实现 需要求一个点在不在圆上简单算法、获取鼠标坐标等等

Object.is() 与原来的比较操作符“=”、“”的区别?

“==”:不全相等,只⽐较数据,不⽐较类型,如果两边的类型不⼀致,则会

进⾏强制类型转化后再进⾏⽐较

“===”:全等,既要⽐较数据,也⽐较数据类型,如果两边的类型不⼀致时,

不会做强制类型准换,直接返回 false

(1) == 主要存在:强制转换成 number, null==undefined

" "==0 //true

“0”==0 //true

" " !=“0” //true

123==“123” //true

null==undefined //true

(2)Object.js:⼀般情况下和三等号的判断相同,它处理了⼀些特殊的情况,

⽐如 -0 和 +0 不再相等,两个 NaN 是相等的。

主要的区别就是+0 != -0 ⽽ NaN==NaN

(相对⽐=的改进)

Proxy 可以实现什么功能?

在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响

应式。

Proxy 是 ES6 中新增的功能,它可以⽤来⾃定义对象中的操作。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler ⽤来⾃定义对象中的操作,⽐如可

以⽤来⾃定义 set 或者 get 函数。

下⾯来通过 Proxy 来实现⼀个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
 let handler = {
 get(target, property, receiver) {
 getLogger(target, property)
 return Reflect.get(target, property, receiver)

 },
 set(target, property, value, receiver) {
 setBind(value, property)
 return Reflect.set(target, property, value) }

 }
 return new Proxy(obj, handler)

}
let obj = { a: 1 }
let p = onWatch(
 obj,
 (v, property) => {
 console.log(`监听到属性${property}改变为${v}`)
 },
 (target, property) => {
 console.log(`'${property}' = ${target[property]}`)
 }
)
p.a = 2 *//* 监听到属性*a*改变
p.a *// 'a' = 2*

在上述代码中,通过⾃定义 set 和 get 函数的⽅式,在原本的逻辑中插⼊了我

们的函数逻辑,实现了在对对象任何属性进⾏读写时发出通知。

当然这是简单版的响应式实现,如果需要实现⼀个 Vue 中的响应式,需要在

get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使⽤ Proxy 替换原本的

API 原因在于 Proxy ⽆需⼀层层递归为每个属性添加代理,⼀次即可完成以上

操作,性能上更好,并且原本的实现有⼀些数据更新不能监听到,但是 Proxy

可以完美监听到任何⽅式的数据改变,唯⼀缺陷就是浏览器的兼容性不好。

Vue 的路由实现:hash 模式 和 history 模式

**hash 模式:**在浏览器中符号“#”,#以及#后面的字符称之为 hash,用

window.location.hash 读取;

特点:hash 虽然在 URL 中,但不被包括在 HTTP 请求中;用来指导浏览器动作,对服务

端安全无用,hash 不会重加载页面。

hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,

因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

**history 模式:**history 采用 HTML5 的新特性;且提供了两个新方法:pushState(),

replaceState()可以对浏览器历史记录栈进行修改,以及 popState 事件的监听到状

态变更。

history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,

如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回

404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支

持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到

任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页

vuex核心流程

每个Vuex应用的核心就是store,里面又包括:

(1)state(数据):用来存放数据源,就是公共状态

(2)getters(数据加工):有时候需要对数据源进行加工,返回需要的数据

(3)actions(事件):要执行的操作,可以进行异步操作

(4)mutations(执行):操作结束之后,actions通过commit更新state数据源

(5)modules:使用单一状态树,致使应用的全部状态集中到一个很大的对象,所以把每个模块的局部状态分装使每一个模块拥有本身的state,mutation,actions,setters,甚至是嵌套子模块

Vuex的工作流程就是:

(1)通过dispatch去提交一个actions

(2)actions接收到这个事件之后,在actions中可以执行一些异步|同步操作,根据不用的情况去分发给不同的mutations

(3)actions通过commit去触发mutations,

(4)mutations去更新state数据,state更新之后,就会通知vue进行渲染

vue-router 有哪几种导航钩子

1、全局守卫: router.beforeEach

2、全局解析守卫: router.beforeResolve

3、全局后置钩子: router.afterEach

4、路由独享的守卫: beforeEnter

5、组件内的守卫: beforeRouteEnter、beforeRouteUpdate (2.2 新增)、

beforeRouteLeave

导航表示路由正在发生改变,vue-router 提供的导航守卫主要用来:通过跳转或取消的

方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组

件级的。

注意:参数或查询的改变并不会触发进入/离开的导航守卫。 你可以通过 观察

$route 对象 来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

vue-router 实现动态加载路由组件( 懒加载 )

当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应

的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

第一步:定义一个能够被 Webpack 自动代码分割的异步组件。

//在 src/router/index.js 里面引入异步引入组件

const index = () => import('../page/list/index.vue');

第二步:在路由配置中什么都不需要改变,只需要像往常一样使用 index。

const router = new VueRouter({

routes: [

{ path: '/index', component: index,name:"index" }

]})

第三步:在 build/webpack.base.conf.js 下的 output 属性,新增 chunkFilename。

output: {

path: config.build.assetsRoot,

filename: '[name].js',

//新增 chunFilename 属性

chunkFilename: '[name].js',

publicPath: process.env.NODE_ENV === 'production'

? config.build.assetsPublicPath

: config.dev.assetsPublicPath

}, 

如何实现文字禁止换行,超过部分用省略号隐藏

white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
强制不换行,超出部分隐藏且以省略号形式出现

MVC、MVP 与 MVVM 模式

1、MVC:

MVC 是应用最广泛的软件架构之一,一般 MVC 分为:

Model( 模型 )、Controller( 控制器 )、View( 视图 )。

这主要是基于分层的目的,让彼此的职责分开。View 一般通过 Controller 来和 Model 进行联系。Controller 是 Model 和 View 的协调者,View 和 Model不直接联系。基本联系都是单向的。

1、View 传送指令到 Controller

2、Controller 完成业务逻辑后,要求 Model 改变状态

3、Model 将新的数据发送到 View,用户得到反馈

2、MVP:

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

1、各部分之间的通信,都是双向的。

2、View 与 Model 不发生联系,都通过 Presenter 传递。

3、View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter 非常厚,所有逻辑都部署在那里。

3、MVVM

MVVM 是把 MVC 的 Controller 和 MVP 的 Presenter 改成了 ViewModel。

View 的变化会自动更新到 ViewModel,ViewModel 的变化也会自动同步到 View上显示。这种自动同步是因为 ViewModel 中的属性实现了 Observer,当属性变更时都能触发对应的操作。

请描述一次完整的 Redux 数据流

看上面的图,可以看到数据流发生改变的时候,数据是如何流动的。

单向数据流调用 store.dispatch(action) -> reducer(state, action) ->store.getState()

(1)调用 sote.dispath(action)(这里的 action 和上面提到的 action 不是一个概念,

这个是一个纯的对象,上面是一个 action creator)

{ type: 'LIKE_ARTICLE', ID: 1 }

你可以在任何地方调用 store.dispatch(action),比如组件内部,Ajax 回调函数里面等等。

(2)Action 会触发给 Store 指定的 rootreducerrootreducer 会返回一个完整的状态树,state 状态树上的各个值都可以由对应的reducer 来更新。

(3)store 会保存状态树

更新完 state 后,新的 State 会替代旧的 State。然后可以添加监听函数

store.subscribe(listener)在回调函数里面可以通过

store.getState()来获取新的 state。这样就能更新整个 UI。只要调用

dispath,state 就会根据reducer对应更新,进而触发监

听函数subscribe,然后触发回调函数渲染 UI。

React的事件和普通的HTML事件有什么不同?

区别:

● 对于事件名称命名⽅式,原⽣事件为全⼩写,react 事件采⽤⼩驼峰;

● 对于事件函数处理语法,原⽣事件为字符串,react 事件为函数;

● react 事件不能采⽤ return false 的⽅式来阻⽌浏览器的默认⾏为,⽽必

须要地明确地调⽤preventDefault()来阻⽌默认⾏为。

合成事件是 react 模拟原⽣ DOM 事件所有能⼒的⼀个事件对象,其优点如

下:

● 兼容所有浏览器,更好的跨平台;

● 将事件统⼀存放在⼀个数组,避免频繁的新增与删除(垃圾回收)。

● ⽅便 react 统⼀管理和事务机制。

事件的执⾏顺序为原⽣事件先执⾏,合成事件后执⾏,合成事件会冒泡绑定到

document 上,所以尽量避免原⽣事件与合成事件混⽤,如果原⽣事件阻⽌冒

泡,可能会导致合成事件不执⾏,因为需要冒泡到document 上合成事件才会

执⾏。

在React中如何避免不必要的render?

React 基于虚拟 DOM 和⾼效 Diff 算法的完美配合,实现了对 DOM 最⼩粒度

的更新。⼤多数情况下,React 对 DOM 的渲染效率⾜以业务⽇常。但在个别

复杂业务场景下,性能问题依然会困扰我们。此时需要采取⼀些措施来提升运

⾏性能,其很重要的⼀个⽅向,就是避免不必要的渲染(Render)。这⾥提

下优化的点:

● shouldComponentUpdate 和 PureComponent

在 React 类组件中,可以利⽤ shouldComponentUpdate或者

PureComponent 来减少因⽗组件更新⽽触发⼦组件的 render,从⽽达到⽬

的。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组

件重新渲染,返回 false 即可。

● 利⽤⾼阶组件

在函数组件中,并没有 shouldComponentUpdate 这个⽣命周期,可以利⽤

⾼阶组件,封装⼀个类似 PureComponet 的功能

● 使⽤ React.memo

React.memo 是 React 16.6 新的⼀个 API,⽤来缓存组件的渲染,避免不必

要的更新,其实也是⼀个⾼阶组件,与 PureComponent ⼗分类似,但不同的

是, React.memo只能⽤于函数组件。

React组件的state和props有什么区别?

(1)props

props是⼀个从外部传进组件的参数,主要作为就是从⽗组件向⼦组件传递数

据,它具有可读性和不变性,只能通过外部组件主动传⼊新的props来重新渲

染⼦组件,否则⼦组件的props以及展现形式不会改变。

(2)state

state的主要作⽤是⽤于组件保存、控制以及修改⾃⼰的状态,它只能在

constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,

只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新

渲染。

(3)区别

● props 是传递给组件的(类似于函数的形参),⽽state 是在组件内被组

件⾃⼰管理的(类似于在⼀个函数内声明的变量)。

● props 是不可修改的,所有 React 组件都必须像纯函数⼀样保护它们的

props 不被更改。

● state 是在组件中创建的,⼀般在 constructor中初始化 state。state 是

多变的、可以修改,每次setState都异步更新的。

slot是什么?有什么作⽤?原理是什么?

slot⼜名插槽,是Vue的内容分发机制,组件内部的模板引擎使⽤slot元素作

为承载分发内容的出⼝。插槽slot是⼦组件的⼀个模板标签元素,⽽这⼀个标

签元素是否显示,以及怎么显示是由⽗组件决定的。slot⼜分三类,默认插

槽,具名插槽和作⽤域插槽。

默认插槽:⼜名匿名查抄,当slot没有指定name属性值的时候⼀个默认

显示插槽,⼀个组件内只有有⼀个匿名插槽。

具名插槽:带有具体名字的插槽,也就是带有name属性的slot,⼀个组

件可以出现多个具名插槽。

作⽤域插槽:默认插槽、具名插槽的⼀个变体,可以是匿名插槽,也可以

是具名插槽,该插槽的不同点是在⼦组件渲染作⽤域插槽时,可以将⼦组

件内部的数据传递给⽗组件,让⽗组件根据⼦组件的传递过来的数据决定

如何渲染该插槽。

实现原理:当⼦组件vm实例化时,获取到⽗组件传⼊的slot标签的内容,存

放在vm. s l o t 中,默认插槽为 v m . slot中,默认插槽为vm. slot中,默认插槽为vm.slot.default,具名插槽为vm.$slot.xxx,

xxx 为插槽名,当组件执⾏渲染函数时候,遇到slot标签,使⽤$slot中的内

容进⾏替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作⽤域

插槽。

对 React 和 Vue 的理解,它们的异同

相似之处:

● 都将注意⼒集中保持在核⼼库,⽽将其他功能如路由和全局状态管理交给

相关的库;

● 都有⾃⼰的构建⼯具,能让你得到⼀个根据最佳实践设置的项⽬模板;

● 都使⽤了Virtual DOM(虚拟DOM)提⾼重绘性能;

● 都有props的概念,允许组件间的数据传递;

● 都⿎励组件化应⽤,将应⽤分拆成⼀个个功能明确的模块,提⾼复⽤性。

不同之处 :

1)数据流

Vue默认⽀持数据双向绑定,⽽React⼀直提倡单向数据流

2)虚拟DOM

Vue2.x开始引⼊"Virtual DOM",消除了和React在这⽅⾯的差异,但是在具

体的细节还是有各⾃的特点。

● Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程

中,会跟踪每⼀个组件的依赖关系,不需要重新渲染整个组件树。

● 对于React⽽⾔,每当应⽤的状态被改变时,全部⼦组件都会重新渲染。

当然,这可以通过 PureComponent/shouldComponentUpdate这个⽣命

周期⽅法来进⾏控制,但Vue将此视为默认的优化。

3)组件化

React与Vue最⼤的不同是模板的编写。

● Vue⿎励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是

多了⼀些属性。

● React推荐你所有的模板通⽤JavaScript的语法扩展⸺JSX书写。

具体来讲:React中render函数是⽀持闭包特性的,所以import的组件在

render中可以直接调⽤。但是在Vue中,由于模板中使⽤的数据都必须挂在

this 上进⾏⼀次中转,所以 import ⼀个组件完了之后,还需要在

components 中再声明下。 4)监听数据变化的实现原理不同

● Vue 通过 getter/setter 以及⼀些函数的劫持,能精确知道数据变化,不需

要特别的优化就能达到很好的性能● React 默认是通过⽐较引⽤的⽅式进⾏的,如果不优化

(PureComponent/shouldComponentUpdate)可能导致⼤量不必要的

vDOM的重新渲染。这是因为 Vue 使⽤的是可变数据,⽽React更强调数

据的不可变。

5)⾼阶组件

react可以通过⾼阶组件(HOC)来扩展,⽽Vue需要通过mixins来扩展。

⾼阶组件就是⾼阶函数,⽽React的组件本身就是纯粹的函数,所以⾼阶函数

对React来说易如反掌。相反Vue.js使⽤HTML模板创建视图组件,这时模板

⽆法有效的编译,因此Vue不能采⽤HOC来实现。

6)构建⼯具

两者都有⾃⼰的构建⼯具:

● React ==> Create React APP

● Vue ==> vue-cli

7)跨平台

● React ==> React Native

● Vue ==> Weex

delete和Vue.delete删除数组的区别

1。 delete:只是被删除数组成员变为 empty / undefined,其他元素键

值不变

2。 Vue.delete:直接删了数组成员,并改变了数组的键值(对象是响

应式的,确保删除能触发更新视图,这个⽅法主要⽤于避开 Vue 不能检

测到属性被删除的限制)

什么是单向数据流

概念:数据流指的是组件之间的数据流动。单向数据流指⽗级数据修改

流向⼦级,⼦级不能修改⽗级的数据,在Vue中,⽗组件可以通过Prop向⼦

组件进⾏传值,当⽗组件的数据发⽣改变时,⽗级的更新会向下流动到⼦级,

⼦组件中的数据会同步更新。但是,反过来则不⾏,也就是说⼦组件中不能通

过修改 Prop 来更新⽗级的数据

React的⽣命周期有哪些?

渲染过程调用到的生命周期函数,主要几个要知道;

* constructor

* getInitialState

* getDefaultProps

* componentWillMount

* render

* componentDidMount

更新过程

* componentWillReceiveProps

* shouldComponentUpdate

* componentWillUpdate

* render

* componentDidUpdate

卸载过程

componentWillUnmount

Component, Element, Instance 之间有什么区别和联系?

React.createClass和extends Component的bai区别主要在于:

1)语法区别

● createClass本质上是⼀个⼯⼚函数,extends的⽅式更加接近最新的

ES6规范的class写法。两种⽅式在语法上的差别主要体现在⽅法的定义

和静态属性的声明上。

● createClass⽅式的⽅法定义使⽤逗号,隔开,因为creatClass本质上是

⼀个函数,传递给它的是⼀个Object;⽽class的⽅式定义⽅法时务必谨

记不要使⽤逗号隔开,这是ES6 class的语法规范。

(2)propType 和 getDefaultProps

● React.createClass:通过proTypes对象和getDefaultProps()⽅法来设置

和获取props.

● React.Component:通过设置两个属性propTypes和defaultProps

(3)状态的区别

● React.createClass:通过getInitialState()⽅法返回⼀个包含初始值的对

● React.Component:通过constructor设置初始状态

(4)this区别

● React.createClass:会正确绑定this

● React.Component:由于使⽤了 ES6,这⾥会有些微不同,属性并不会⾃

动绑定到 React 类的实例上。

(5)Mixins● React.createClass:使⽤ React.createClass 的话,可以在创建组件时添

加⼀个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给

mixins。

● 如果使⽤ ES6 的⽅式来创建组件,那么 React mixins 的特性将不能被使

式的⽅法定义使⽤逗号,隔开,因为creatClass本质上是

⼀个函数,传递给它的是⼀个Object;⽽class的⽅式定义⽅法时务必谨

记不要使⽤逗号隔开,这是ES6 class的语法规范。

(2)propType 和 getDefaultProps

● React.createClass:通过proTypes对象和getDefaultProps()⽅法来设置

和获取props.

● React.Component:通过设置两个属性propTypes和defaultProps

(3)状态的区别

● React.createClass:通过getInitialState()⽅法返回⼀个包含初始值的对

● React.Component:通过constructor设置初始状态

(4)this区别

● React.createClass:会正确绑定this

● React.Component:由于使⽤了 ES6,这⾥会有些微不同,属性并不会⾃

动绑定到 React 类的实例上。

(5)Mixins● React.createClass:使⽤ React.createClass 的话,可以在创建组件时添

加⼀个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给

mixins。

● 如果使⽤ ES6 的⽅式来创建组件,那么 React mixins 的特性将不能被使

⽤了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值