面试题(二)

1、事件冒泡和事件委托

  • 事件冒泡
<div @click="divClick">
	<span @click="spanClick">123span>
div>

代码中,点击span标签,不仅会触发span标签中的spanClick方法,还会触发div中的divClick方法,这个过程叫事件冒泡。事件从目标元素逐渐向上,由内到外的。

  • 事件委托
<ul class='ul'>
	<li>1li>
	<li>2li>
	<li>3li>
ul>

$('ul').bind('click','li',function() {})
给父元素添加侦听事件,利用事件冒泡,影响每个子节点,叫事件委托。

优点:减少事件注册,提高网页性能;新插入的元素,也能绑定上事件。

2、vue的for循环中,为什么要,用key的作用,为什么要用key

  • 例如:在没有绑定数据的情况下,存在两个输入框,typeEmail为 true 的时候,显示email标签,否则显示password标签,当输入email的时候,切换类型,password标签显示的数据是之前email的数据。
    <div v-if="typeEmail">
      <label>邮箱:label>
      <input placeholder="email">
    div>
    <div v-else>
      <label>密码:label>
      <input placeholder="password">
    div>
    <button @click="changeType">修改类型button>
  div>

面试题(二)_第1张图片
或者如下: li标签里包含复选框,不加key时,点击 添加 按钮,从 list 头部插入新数据。当选中 李四 时,点击添加 111,添加成功后,选中项则变成了 111.




  • key的作用:更高效的更新虚拟dom(在视图层和数据层建立一个一一对应的关系,方便进行局部刷新)
  • 使用时需要注意:
    • 一般需要动态绑定
    • 确保 key 属性的值是唯一的,类型多为 字符串、数字

3、js事件循环 EventLoop

EventLoop由三部分构成:调用栈(call stack)、宏任务(如消息队列(Message Queue))、微任务队列(Microtask Queue)
执行顺序:首先执行调用栈,调用栈里面事件执行完毕后,弹出去,继而把微任务(promise)里的事件推到调用栈里执行、弹出,最后把消息队列(setTimeOut)的事件推到调用栈去执行、弹出。

微任务 执行时机比 宏任务 早
宏任务:setTimeout、setInterval、Dom事件、ajax请求
微任务:promise.then,async/await
process.nextTikc(优先级略高)
微任务 > Dom渲染 > 宏任务

可参考:2分钟了解 JavaScript Event Loop
宏任务与微任务的执行顺序

4、js实现promise,promise.all

  • promise
function MyPromise (fn) {
  let that = this;
  that.status = "pending";
  that.value = "";
  function resolve (val) {
    if(that.status === "pending") {
      that.status = "resolve";
      that.value = val;
    }
  }
  function reject (val) {
    if(that.status === "pending") {
      that.status = "reject";
      that.value = val;
    }
  }
  fn(resolve,reject)
}
MyPromise.prototype.then = function (onResolve,onReject) {
  let that = this;
  if(that.status === "resolve") {
    onResolve(that.value);
  } else {
    onReject(that.value);
  }
}
let p1 = new MyPromise((resolve,reject)=>{
  resolve('成功了');
  // reject("失败了")
}).then((result) => {
  console.log(result)
}, (error) => {
  console.log(error)
})
  • Promise.all
Promise.all2 = function (arr) {
  return new Promise((resolve, reject) => {
    let num = 0,
      result = [];
    for(let i = 0; i < arr.length; i++){
      //将arr的每一项都改为promise类型
      Promise.resolve(arr[i]).then((value) => {
        num ++;
        result.push(value); //arr[i]执行成功后,放入数组
        if(num === arr.length) { //当执行完所有的arr以后,状态修改为resolve,将结果数组retrun出去
          return resolve(result)
        }
      },(error) => {
      	//若arr有执行出错是,状态直接改为reject
        reject(error)
      })
    }
  })
}
let p1 = new Promise((resolve,reject) => {
  setTimeout(() => {
    resolve('reject111');
  },1000)
});
let p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('reject222');
  },5000)
});
Promise.all2([p1,p2]).then((res) => {
  console.log(res)
},(error)=> {
  console.log(error)
})
//[ 'reject111', '222' ]

5、vue双向绑定原理

学习地址

  • 1、定义一个 Vue 的类
    • construct 方法里面:
      • 将接收到的数据进行重新赋值 this.$data this.$el
      • 通过 Object.defineProperty进行数据劫持,把this.$data中的每个数据添加get set方法; — Observe()
      • 通过 Object.defineProperty方法,将this.$data里的数据,挂载到 this对象上
      • 将 html 里的模板进行编译(把页面中的 {{**}} 替换为真实的 this.$data 的数据) — Compile()
    • 数据劫持方法Observe(obj)
      • 遍历 obj,将this.$data中的每个键值对都增加 get 和 set 方法,方便后续进行数据监听
      • get 方法中,将 watcher 对象添加到 dep.subs 订阅列表里面,方便后面通知dom修改ui上的数据
      • set 方法中,通知 dom 进行数据更新(数据做了修改,就要更新 ui)
    • 模板编译 Compile()
      • 创建文档碎片,将需要检测到dom放到碎片中
      • 获取碎片中文本节点中的文本
      • 将文本中的 {{**}}替换成真实的数据
      • 创建一个watch儿实例,将这个node节点放到订阅列表,回调里面更新数据(this.$data数据修改,实时更新ui)
  • 创建dep类
    • 里面放一个数组,是为了接受 watcher 对象
    • 一个新增 watcher 对象的方法
    • 一个通知的方法(数据更新,通知dom更新)
  • 创建watcher类
    • 接收vm(Vue实例中的数据)、key(dom对应的键名)、cd(数据更新的回调)

demo:

DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>手动双向数据绑定title>
head>
<body>
  <div id="app">
    <div>姓名:{{name}}div>
    <div>年龄:{{age}}div>
    <div>信息:{{info.a}}div>
    <div>
      姓名:<input type="text" v-model="name">
    div>
    <div>
      信息:<input type="text" v-model="info.a">
    div>
  div>
body>
<script src="./vue.js">script>
<script>
  let vm = new Vue({
    el: "#app",
    data: {
      name: "张三",
      age: "20",
      info: {
        a: "1",
        c: "2"
      }
    }
  })
script>
html>
class Vue {
  constructor(options) {
    this.$data = options.data;
    Observe(this.$data)   //数据劫持
    //将this.$data[key]挂载到this[key]中
    Object.keys(this.$data).forEach(key => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return this.$data[key];
        },
        set(newVal) {
          this.$data[key] = newVal;
        }
      })
    })
    //调用模板编译方法
    Compile(options.el, this)
  }
}
//定义一个数据劫持的方法
function Observe (obj) {
  if(!obj || typeof obj !== "object") {return}  //递归终止条件
  console.log(obj)
  let dep = new Dep();
  Object.keys(obj).forEach(key => { //获取当前obj上的每一个属性
    let val = obj[key]; //当前被循环的 key 对应的属性值
    Observe(val); //把 val 这个子节点进行递归
    Object.defineProperty(obj, key, { //需要为当前的 key 对应的属性添加 getter 和 setter
      enumerable: true,
      configurable: true,
      get() {
        Dep.target && dep.addSub(Dep.target);
        return val;
      },
      set(newVal) {
        val = newVal;
        dep.notify();
        Observe(val);
      }
    })
  })
}
//对 HTML 进行模板编译对方法
function Compile (el, vm) {
  vm.$el = document.querySelector(el);
  //创建文档碎片(通过碎片更新dom比直接修改dom元素性能要好,直接修改dom会使页面进行重绘或重排)
  let fragment = document.createDocumentFragment();

  while(childNode = vm.$el.firstChild) { //便利el到第一个子元素,存在就从页面中移入碎片中
    fragment.appendChild(childNode)
  }
  replace(fragment)

  function replace (node) { //将实际值与{{**}}进行替换
    if(node.nodeType === 3) { //当前的 node 节点是文本节点时,利用正则进行替换
      let regMustache = /\{\{\s*(\S+)\s*\}\}/,
        text = node.textContent, //获取文本节点的字符串内容
        execResult = regMustache.exec(text);  //进行字符串的正则匹配与提取
      if(execResult) {
        let nValue = execResult[1].split('.').reduce((newVal,key) => {return newVal[key]}, vm);
        node.textContent = text.replace(regMustache,nValue);  //文本节点的{{**}}替换成实际值
        new Watcher(vm, execResult[1], (newVal) => { //将dom放入消息订阅列表,便于后续数据改变后,告诉dom去更新显示数据
          node.textContent = text.replace(regMustache, newVal);
        })
      }
      return  //终止递归
    }

    if(node.nodeType === 1 && node.tagName.toUpperCase() === "INPUT") {
      let findResult = Array.from(node.attributes).find((item) => item.name === "v-model");
      if(findResult) {  //input元素存在v-model属性,才会执行后面的逻辑
        let val = findResult.value.split('.').reduce((newVal, key) => newVal[key], vm);
        node.value = val; //input元素绑定数据
        //将 input 添加到订阅列表
        new Watcher(vm, findResult.value, (newVal) => {
          node.value = newVal;
        })
        //对input绑定一个监听事件,确保输入时,更新 vm 数据
        node.addEventListener('input',function (e) {
          let keyArr = findResult.value.split('.'),
              obj = keyArr.slice(0,keyArr.length - 1).reduce((newVal, key) =>newVal[key], vm);
          obj[keyArr[keyArr.length-1]] = e.target.value;
        })
      }
    }
    node.childNodes.forEach(child => {replace(child)}) //存在子节点时,递归处理文本
  }
  vm.$el.appendChild(fragment)
}
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(watcher) {
    this.subs.push(watcher); //向subs数组中添加订阅者
  }
  notify() {
    this.subs.forEach((watcher) => {watcher.update()}); //数据赋值时,调用该方法,更新dom显示数据
  }
}
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    this.key = key;
    this.cb = cb;
    Dep.target = this;  //这里获取一下 vm 的值,将 watcher 添加到 dep。subs 中
    key.split('.').reduce((newVal, key) => newVal[key],vm);
    Dep.target = null;
  }
  update() {
    //获取更新后的值
    let newVal = this.key.split('.').reduce((newVal, key) => newVal[key], this.vm);
    this.cb(newVal);
  }
}
  • 最简单的发布订阅者模式
//收集订阅者/
class Dep {
  constructor() {
    this.subs = [] //subs用来存放所有订阅者
  }
  addSub(watcher) {
   this.subs.push(watcher)  //向subs数组中添加订阅者
  }
  notify() {
    this.subs.forEach((sub) => {sub.update()})  //发布通知
  }
}
//订阅者
class Watcher {
  constructor(cb) {
    this.cb = cb;
  }
  update() {
    this.cb();  //触发回调到方法
  }
}
let w1 = new Watcher(() => {
  console.log("第1个消息订阅者")
})
let w2 = new Watcher(() => {
  console.log("第2个消息订阅者")
})
// w1.update()
// w2.update()
let dep = new Dep();
dep.addSub(w1)
dep.addSub(w2)
//只要为 vue 中的 data 数据重新赋值了,这个赋值动作会被 vue 监听到
//然后 vue 要把数据的变化,通知到每个订阅者
//接下来订阅者(dom)要根据最新到数据更新自己到文本内容节点
dep.notify()
//结果打印出
//第1个消息订阅者
//第2个消息订阅者

6、什么是节流和防抖

  • 节流
    • 场景:通过输入框去请求搜索数据,用户不断进行输入操作,会不断请求接口,导致服务器压力较大,前端数据处理可能出现问题。
    • 如何做:设置一个时间间隔,比如5秒。用户输入,就在输入完成的时间节点往后推迟5秒,连续再次输入,则在输入完毕的节点再次往后推迟5秒,以此类推,用户不断输入,起始时间就不断重置,取最后一次动作的时间为起始时间,5秒之后,再去请求接口处理,数据。
  • 防抖
    • 场景:页面上一个按钮,点击按钮请求接口。若用户不断点击按钮,就会不断去请求接口,导致服务器压力较大,前端数据处理可能会先问题。
    • 如何做:设置一个时间间隔,如5秒。用户连续点击按钮时,每5秒,才会去请求一次接口,处理数据。
  • 区别:节流是指一定时间内,js方法只执行一次;防抖是指频繁触发的情况下,只有足够的空闲时间,在执行一次代码。

7、setTimeout和new promise回调哪个会先执行

promise先执行,因为peomise是微任务,setTimeout是宏任务。js事件循环机制中,执行栈中的同步任务先执行,全部执行完毕后弹出,之后微任务推入到执行栈,执行完毕后从执行栈中弹出,最后宏任务推入执行栈,执行完毕弹出。

  • 注:
    微任务:如new Promise构造方法的回调
    宏任务:如setTimeout、setInterval、promise.then回调

8、vue修饰符有哪些

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

<a v-on:click.stop="doThis">a>


<form v-on:submit.prevent="onSubmit">form>


<a v-on:click.stop.prevent="doThat">a>


<form v-on:submit.prevent>form>



<div v-on:click.capture="doThis">...div>



<div v-on:click.self="doThat">...div>

9、webpack打包,作用

1)谈谈你对webpack的理解?

webpack是一个打包模块化js的工具,在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。WebPack可以看做是模块的打包机器:它做的事情是,分析你的项目结构,找到js模块以及其它的一些浏览器不能直接运行的拓展语言,例如:Scss,TS等,并将其打包为合适的格式以供浏览器使用。

2)说说webpack与grunt、gulp的不同?

三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据,整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。所以,从构建思路来说,gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系;webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工对于知识背景来说,gulp更像后端开发者的思路,需要对于整个流程了如指掌webpack更倾向于前端开发者的思路

3)什么是bundle,什么是chunk,什么是module?

bundle:是由webpack打包出来的文件;chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割;module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块

4)什么是Loader?什么是Plugin?

Loader是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中;
Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。

5)有哪些常见的Loader?他们是解决什么问题的?

file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件;
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去;
source-map-loader:加载额外的 Source Map 文件,以方便断点调试;
image-loader:加载并且压缩图片文件;
babel-loader:把 ES6 转换成 ES5;
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性;
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
eslint-loader:通过 ESLint 检查 JavaScript 代码

10、apply、call、bind的区别

apply和call
  • 作用完全一样,都是为了改变某个函数运行时的上下文而存在,换句话,就是改变函数体内的this指向。
  • 写法不同。
    • apply 最多只能接受两个参数-新的 this 对象和一个数组。如果要给这个方法传递多个参数,要把参数全部写进数组里面,即使只有一个参数,也要写到数组里。 写法如下:func.call(func1,arg1,arg2)
    • call 接收多个参数,第一个是新的 this 对象和一系列参数。写法如下:func.call(func2,arg2,arg2,arg3)
bind
  • MDN解释是:bind()方法会创建一个新的函数,称为绑定函数,当调用这个 绑定函数 时,绑定函数会以创建它时传入 baind()方法的第一个参数作为 this,传入 baind()方法的第二个及以后的参数加上绑定函数运行时,本身的参数按照顺序作为原函数的参数来调用原函数。写法如下:
var bar = function(){
	console.log(this.x);
}
var foo = {
	x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3
三者比较:
ar obj = {
    x: 81,
};
var foo = {
    getX: function() {
        return this.x;
    }
}
console.log(foo.getX.bind(obj)());  //81
console.log(foo.getX.call(obj));    //81
console.log(foo.getX.apply(obj));   //81
  • 代码可以看出,三者写法上是有区别的,特别是bind()方法,后面多了个括号
  • 当希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用bind()方法。apply和call会立即执行函数
  • 最后汇总:
    • 三者都是用来改变函数的this指向的
    • 三者第一个参数都是this要指向的对象
    • 三者都是可以利用后续参数传参
    • bind 是返回对应函数,便于稍后调用,apply、call则是立即调用

11、如何解决跨域问题

  • 是什么
    跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
  • 原因
    1、前后端分离导致两边地址不同。
    2、浏览器的同源策略。
  • 解决方法
    • jsonp(只能用get方法)
      1、可以使用script标签,script的src没有同源限制,写法如下:
<script src= "http://baidu.com:3000/callback=getData">script>
<script>
function getData(res) {
	console.log(res)
}
script>

2、ajax中
dataType: 'jsonp', // 请求方式为jsonp jsonpCallback: "onBack", // 自定义回调函数名
3、vue中


this.$http.jsonp('http://www.demo2.com:8080/login', {
    params: {},
    jsonp: 'onBack'
}).then((res) => {
    console.log(res); 

    • CORS原理(Access-Control-Allow-Origin)
      服务器做改造,响应头上添加 Access-Control-Allow-Origin属性
    • Nginx
      nginx反向代理中设置proxy_cookie_domain 和 八、NodeJs中间件代理中cookieDomainRewrite参数的设置。
    • vue中设置代理(proxy)
devServe: {
	proxy: {
		'/api': {
			target: "http://*****:8888", //后台的服务地址
			changeOrigin: true, //允许跨域
			pathReweite: {
				'^/api':"" //重写路径
			}
		}
	}
}

拓展

  • 什么是同源策略
    同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
  • 同源策略限制了一下行为:
    Cookie、LocalStorage 和 IndexDB 无法读取
    DOM 和 JS 对象无法获取
    Ajax请求发送不出去

12、性能优化有哪些

1)减少请求数量

使用雪碧图、base64、字体图标
使用缓存

2)减少资源大小

css压缩
图片压缩
js压缩(删除无效字符及注释)

3)优化资源加载

css放到header中,优先使用外链
js放到body地步,优先使用外链
模块按需加载、异步加载(webpack)
资源懒加载、按需加载

13、如何封装公共组件


假设要封装如上的组件:left默认是一条线,颜色可配置,ui也可配置为图标;body标题可以配置;right 按钮也可配置,默认更多,绑定事件

1)首先要明确组件中有哪些内容-》left、body、right
2)对可配置的属性(如 线条颜色、标题名称、按钮名称),通过 prop 属性接收,default 有默认值
3)right 部分可能不是按钮,这种情况要使用插槽,默认是 更多,通过 $emit 向上传递一个方法,不是按钮,就在父组件里插入一个插槽

14、$nextTick原理及使用场景

1)使用场景:
  • 获取数据更新之后的 dem
  • created()中进行 dom 操作(created钩子函数执行的时候,dom并未开始渲染,这个时候进行dom操作是没有用的)
  • 获取元素的宽度
    注:mounted 不会承诺所有的子组件也都一起被挂载。
2)原理

数据可能是同步获取的,也可能是异步获取的,获取到数据以后,去渲染页面,这个过程是异步的,在 mounted 回调中,不能保证子组件的 dom 已经完全渲染到了页面上,$nextTick 会在数据循环渲染到页面之后,去调用里面的回调方法。

15、js异步操作有哪些

  • setTimeout、setInterval
  • ajax 异步请求
  • promise.then
  • 回调函数
  • 时间监听

16、vue 的 hash 模式和 history 模式的区别

  • hash 模式下,仅 hash 符号前面的内容会被包含在请求中,hash值对后端没有影响,改变 hash 不会重新加载页面
  • history 模式下, 前端 url 必须和时机向后端发起的 url 一致,否则会返回 404 错误

17、如何获取 hash 值

  • this.$route.fullPath
    面试题(二)_第2张图片
  • window.location.hash

18、全局对象 window 上有哪些方法

1、window.alert/window.confirm //浏览器弹框、确认框
2、window.open //打开新窗口
3、window.setTimeout / window.setInterval //定时
4、window.scrollBy(): //滚动
5、window.navigator //获取浏览器信息

19、Object.defineProperty的作用

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

var obj = {};
Object.defineProperty(obj, 'attr',{
    value: 12,
    writable: false	//是否可修改
})
console.log(obj) // {attr: 12}

20、深拷贝方法有哪些

  • 使用JSON.stringify()以及JSON.parse()
  • lodash 实现 const b=_.cloneDeep(a)
  • 通过递归实现
  • $.extend 实现

21、vue 中修改数组

this.$set(this, 'list', [8,9,0])

22、js阻止事件冒泡

<a href="https://www.csdn.net/" class="box">
	<button class="btn">按钮</button>
</a>
  • event.stopPropagation()这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开。
  • event.preventDefault()这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
  • return false ;

23、addEventListener回调的参数

addEventListener(event,callback,useCapture)

  • event: 必须,字符串,事件名(click,focus)
  • callback:必须,指定事件触发时,要执行的函数
  • useCapture:可选,布尔值,指定事件是否在捕获或者冒泡阶段执行(true,事件在捕获阶段执行,false事件在冒泡阶段执行)
    注:useCapture 为true或者false并不会阻止事件冒泡,只是通过修改子元素、父元素绑定的事件的顺序,去实现功能。

24、vue 自定义指令

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

//注册局部指令
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}
<input v-focus>

参考教程

24、js 判断数据类型

console.log(typeof "a")		//'string'
console.log([1,2,3] instanceof Array) //true
console.log([1,2,3].constructor === Array) //true
console.log(Object.prototype.toString.call({a:1}) === '[object Object]') 	//true
console.log(Array.isArray(array))	 //true

a instanceof b 中, a 是否是 b 实例化构造出来的,a 是否为 b 的实例对象。原型 原型链

25、浏览器兼容性问题

26、获取对象属性的方法

Object.keys() Object.getOwnPropertyNames()

var obj = {a:1,b:2};
console.log(Object.keys(obj));	//['a', 'b']
console.log(Object.getOwnPropertyNames(obj)); //['a', 'b']

27、keep-alive 的生命周期

activated deactivated

28、vue中的 data 的使用方法或者对象的区别

//为对象时:
let vm = new Vue({
    el: "#app",
    data: {
      name: "张三",
      age: "20",
      info: {
        a: "1",
        c: "2"
      }
    }
  })
//为方法时-----------------------------------------------
export default {
  name: "HomeView",
  data() {
    return {
      list: [1,2,3]
    }
  }
}

当 data 定义为对象时,表示所有的组件实例共用了一份 data 数据,后续无论在哪个组件实例中修改 data,会影响到其他组件实例。
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例各自的数据。(写成函数,会有函数作用域的概念,是私有函数,只作用到当前组件。)
单纯写成对象的话,就时所有的组件实例共用了一份data,会造成一个变了,其他都跟着改变。

29、js中数据是怎么存储的

js 中,数据分为基本数据类型(String、Number、Bollean、Undefined、Null),和引用数据类型(Array、Object)。其中,基本数据类型都是简单的数据段,存储在栈内存中,引用数据类型存储在堆内存中,然后在栈内存中保存一个对堆内存中实际对象的引用(可以理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的)。
面试题(二)_第3张图片

面试题(二)_第4张图片

30、http和https的区别

参考教程

31、js的继承

你可能感兴趣的:(总结,前端)