说说你对SPA单页面的理解,它的优缺点分别是什么?

是一种只需要将单个页面加载到服务器之中的web应用程序。当浏览器向服务器发出第一个请求时,服务器会返回一个index.html文件,它所需的js,css等会在显示时统一加载,部分页面按需加载。url地址变化时不会向服务器在请求页面,通过路由才实现页面切换。

优点:

  • 良好的交互体验,用户不需要重新刷新页面,获取数据也是通过Ajax异步获取,页面显示流畅;
  • 良好的前后端工作分离模式。

缺点:

  • SEO难度较高,由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势。
  • 首屏加载过慢(初次加载耗时多)

SPA单页面的实现方式有哪些? 

  • 在hash模式中,在window上监听hashchange事件(地址栏中hash变化触发)驱动界面变化;
  • 在history模式中,在window上监听popstate事件(浏览器的前进或后退按钮的点击触发)驱动界面变化,监听a链接点击事件用history.pushState、history.replaceState方法驱动界面变化;
  • 直接在界面用显示隐藏事件驱动界面变化。

说说你对MVC、MVP、MVVM模式的理解 

在以往开发程序过程中,界面布局代码、界面交互逻辑代码、业务逻辑代码三者代码都是混在一起的。随着业务需求越来越大,代码越来越复杂,不仅导致开发困难,更是导致维护代码更困难,特别是维护别人的代码。

所以就出现MVC模式来解决这个问题,其中M代表Model,专门来处理、存储数据。V代表View,专门来处理页面的展示。C代表Controller专门处理业务逻辑。

用户操作View,View发送指令到Control,完成业务逻辑处理后,要求Model处理相应的数据,将处理好的数据发送到View,要求View把这些数据展示给用户。

当然用户也可以直接下发指令到Control,完成对应业务逻辑处理后,要求Model处理相应的数据,将处理好的数据发送到View,要求View把这些数据展示给用户。

也可以通过View直接要求Moder处理数据,将处理好的数据发送到View,要求View把这些数据展示给用户。

然而在MVC模式中,Model、Control、View三者相互依赖,修改起来要兼顾其他两者,还是非常困难。

所以又出现了MVP模式来解决这个问题,在MVP模式中P代表Presenter替代原来的Control。

当用户操作View,View发送指令到Presenter,完成业务逻辑处理后,要求Model处理相应的数据,将处理好的数据返回到Presenter中,Presenter将数据发送到View中,要求View把这些数据展示给用户。

MVP模式中,Presenter将View和Model完全隔离开,Presenter和View相互依赖,Presenter和Model相互依赖,View和Model不再相互依赖,使代码耦合降低。

因为Presenter和View相互依赖,这样Presenter就没办法单独做单元测试,非得等到View做好以后才行。所以对View分割一部分叫做View接口,Presenter只依赖View接口,这样Presenter不用依赖View就可以测试了,并且也增加了复用性,只要View实现了View接口部分,Presenter就可以大发神威。

然而在MVP模式中,因为让Presenter发送数据到View,让View展示,仍然需要大量的、烦人的代码,这实在是一件不舒服的事情。 那么可不可以让View在Model变化时自动更新。

所以出现了MVVM模式来实现这个设想,其中VM代表ViewModel负责视图显示逻辑和监听视图变化,M代表Model变成处理业务逻辑和数据。

当用户操作View时,ViewModel监听到View的变化,会通知Model中对应的方法进行业务逻辑和数据处理,处理完毕后,ViewModel会监听到自动让View做出相应的更新。ViewModel可以对应多个View,具有很强的复用性。

在Vue项目中。new Vue()就是一个ViewModel,View就是template模板。Model就是Vue的选项如data、methods等。在开发过程我们只关注View怎么展示,Model怎么处理业务逻辑和数据。不要去管处理业务逻辑和数据后怎么让View更新,View上有操作,怎么让Model处理这个操作,这些通通交给ViewModel来实现,大大降低了开发成本。

说说你对Object.defineProperty的理解 

  • Object.defineProperty(obj,prop,descriptor)方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
    • obj:要在其上定义属性的对象。
    • prop:要定义或修改的属性的名称。
    • descriptor:将被定义或修改的属性描述符。
  • descriptor属性描述符主要有两种形式:数据描述符和存取描述符。描述符必须是这两种形式之一;不能同时是两者。
    • 数据描述符和存取描述符共同拥有
      • configurable:特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。默认为false。
      • enumerable:当该属性的enumerable为true时,该属性才可以在for...in循环和Object.keys()中被枚举。默认为false。
    • 数据描述符
      • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined。
      • writable:当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为false。
    • 存取描述符
      • get:一个给属性提供 getter的方法,如果没有getter则为undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认为undefined。
      • set:一个给属性提供 setter的方法,如果没有setter则为undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为undefined。
  • 定义descriptor时,最好先把这些属性都定义清楚,防止被继承和继承时出错
function Archiver() {
    var temperature = null;
    var archive = [];
    Object.defineProperty(this, 'temperature', {
        get: function() {
          console.log('get!');
          return temperature;
        },
        set: function(value) {
          temperature = value;
          archive.push({ val: temperature });
        }
    });
    this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

 说说你对Proxy的理解

官方定义:proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

通俗来说是在对目标对象的操作之前提供了拦截,对外界的操作进行过滤和修改某些操作的默认行为,可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象。

let proxy = new Proxy(target, handler)

  • target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);
  • handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数,也就是自定义的行为。

handle可以为{},但是不能为null,否则会报错

Proxy 目前提供了 13 种可代理操作,比较常用的

  • handler.get(target,property,receiver)获取值拦截
  • handler.set(target,property,value,receiver)设置值拦截
  • handler.has(target,prop)in 操作符拦截
let obj = {
	a : 1,
	b : 2
}
let test = new Proxy(obj,{
    get : function (target,property) {
        return property in target ? target[property] : 0
    },
    set : function (target,property,value) {
        target[property] = 6;
    },
    has: function (target,prop){
        if(prop == 'b'){
            target[prop] = 6;
        }
        return prop in target;
    },
})

console.log(test.a);        // 1
console.log(test.c);        // 0

test.a = 3;
console.log(test.a)         // 6

if('b' in test){
    console.log(test)       // Proxy {a: 6, b: 6}
}

 Object.defineProperty和Proxy的区别

  • Object.defineProperty
    • 不能监听到数组length属性的变化;
    • 不能监听对象的添加;
    • 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
  • Proxy
    • 可以监听数组length属性的变化;
    • 可以监听对象的添加;
    • 可代理整个对象,不需要对对象进行遍历,极大提高性能;
    • 多达13种的拦截远超Object.defineProperty只有get和set两种拦截。

你认为Vue的核心是什么? 

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。 

以上是官方原话,从中可以得知Vue的核心是模板语法和数据渲染。 

说说你对单向数据流和双向数据流的理解 

单向数据流是指数据只能从父级向子级传递数据,子级不能改变父级向子级传递的数据。

双向数据流是指数据从父级向子级传递数据,子级可以通过一些手段改变父级向子级传递的数据。

比如用v-model.sync来实现双向数据流。

什么是双向绑定?原理是什么? 

双向绑定是指数据模型(Module)和视图(View)之间的双向绑定。

其原理是采用数据劫持结合发布者-订阅者模式的方式来实现。

Vue中先遍历data选项中所有的属性(发布者)用Object.defineProperty劫持这些属性将其转为getter/setter。读取数据时候会触发getter。修改数据时会触发setter。

然后给每个属性对应new Dep(),Dep是专门收集依赖、删除依赖、向依赖发送消息的。先让每个依赖设置在Dep.target上,在Dep中创建一个依赖数组,先判断Dep.target是否已经在依赖中存在,不存在的话添加到依赖数组中完成依赖收集,随后将Dep.target置为上一个依赖。

组件在挂载过程中都会new一个Watcher实例。这个实例就是依赖(订阅者)。Watcher第二参数式一个函数,此函数作用是更新且渲染节点。在首次渲染过程,会自动调用Dep方法来收集依赖,收集完成后组件中每个数据都绑定上该依赖。当数据变化时就会在seeter中通知对应的依赖进行更新。在更新过程中要先读取数据,就会触发Wacther的第二个函数参数。一触发就再次再次自动调用Dep方法收集依赖,同时在此函数中运行patch(diff运算)来更新对应的DOM节点,完成了双向绑定。

什么是虚拟DOM? 

虚拟DOM是将状态映射成视图的众多解决方案中的一种,其是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染生成真实DOM,在渲染之前,会使用新生成的虚拟节点树和上一次虚拟节点树进行对比,只渲染不同的部分。 

Vue中如何实现一个虚拟DOM?说说你的思路 

首先要构建一个VNode的类,DOM元素上的所有属性在VNode类实例化出来的对象上都存在对应的属性。例如tag表示一个元素节点的名称,text表示一个文本节点的文本,chlidren表示子节点等。将VNode类实例化出来的对象进行分类,例如注释节点、文本节点、元素节点、组件节点、函数式节点、克隆节点。

然后通过编译将模板转成渲染函数render,执行渲染函数render,在其中创建不同类型的VNode类,最后整合就可以得到一个虚拟DOM(vnode)。

最后通过patch将vnode和oldVnode进行比较后,生成真实DOM。

Vue实例挂载的过程是什么? 

在初始化的最后,如果检测到选项有el属性,则调用vm.$mount方法挂载vm,挂载的目标就是把模板渲染成最终的DOM

  • 第一步:确保vm.$options有render函数。

因为在不同构建版本上的挂载过程都不一样,所以要对Vue原型上的$mount方法进行函数劫持。

首先创建一个变量mount将Vue原型上的$mount方法保存到这个变量上。然后Vue原型上的$mount方法被一个新的方法覆盖。在这个新方法中调用mount这个原始方法。

通过el属性进行获取DOM元素。如果el是字符串,则使用document.querySelector获取DOM元素并赋值给el。如果获取不到,则创建一个空的div元素并赋值给el。如果el不是字符串,默认el是DOM元素,不进行处理。

判断el是不是html元素或body元素,如果是则给出警告退出程序。

因为挂载后续过程中需要render函数生成vnode,故要判断$options选项中是否有render函数这个属性,如果有直接调用原始的$mount方法。

如果没有,则判断template是否存在。若不存在则将el的outerHTML赋值给template。若存在,如果template是字符串且以#开头,通过选择符获取DOM元素获取innerHTML赋值给template,如果template已经是DOM元素类型直接获取innerHTML赋值给template。

然后将template编译成代码字符串并将代码字符串转成render函数,并赋值到vm.$options的render属性上。

最后调用原始的$mount方法。

  • 第二步: 在原始的$mount方法,先触发beforeMount钩子函数,然后创建一个Watcher实例,在第二参数传入一个函数vm._update

该函数是首次渲染和更新渲染作用,参数为render函数(vnode),如果vm._vnode不存在则进行首次渲染。

同时vnode中被劫持的数据自动收集依赖。当vnode中被劫持的数据变化时候触发对应的依赖,从而触发vm._update进行更新渲染。

最后触发mounted钩子函数。

Vue为什么要求组件模板只能有一个根元素? 

当前的virtualDOM差异和diff算法在很大程度上依赖于每个子组件总是只有一个根元素。 

axios是什么?怎样使用它?怎么解决跨域的问题? 

axios 是一个基于 promise 的 HTTP 库,先封装在使用。

使用proxyTable配置解决跨域问题。

比如你要调用http://172.16.13.205:9011/getList这个接口

先在axios.create()配置baseURL增加标志

const service = axios.create({
  baseURL: '/api',
});

 service.get(getList, {params:data});

然后在config/index.js文件中配置 

dev:{
    proxyTable: {
        '/api': {
            target: 'http://172.16.13.205:9011', // 设置你调用的接口域名和端口号
            secure: false,
            changeOrigin: true,// 跨域
            pathRewrite: {
                '^/api': '' // 去掉标志
            }
        }
    },
}

 配置后要重新npm run dev

F12中看到请求是http://localhost:8080/api/getList,实际上请求是http://172.16.13.205:9011/getList

你有使用过JSX吗?说说你对JSX的理解? 

JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。 

如果想扩展某个现有的Vue组件时,怎么做呢? 

  • 用mixins混入
  • 用extends,比mixins先触发
  • 用高阶组件HOC封装

Vue渲染大量数据时应该怎么优化?说下你的思路! 

懒加载和分页 

vue-loader是什么?它有什么作用? 

vue-loader是一个webpack的loader,是一个模块转换器,用于把模块原内容按照需求转换成新内容。

它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。可以解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的loader去处理。 

Logo

前往低代码交流专区

更多推荐