不同类型的存储方式:
symbol类型介绍
Symbol
对象是es6
中新引进的一种数据类型,它的作用非常简单,就是用于防止属性名冲突而产生。还可以实现属性的私有化(private)。
Symbol
的最大特点就是值是具有唯一性,这代表使用Symbol
类型的值能做独一无二的一些事情。此外,Symbol
没有构造函数,这使得我们不能new
它,直接使用即可。
symbol类型应用场景:
// 一个班级有两个同学,都叫张三,通过
// 存储和获取的时候通过Symbol()来操作,就唯一了
let user1 = {
name: '张三',
key: Symbol()
};
let user2 = {
name: '张三',
key: Symbol()
};
let score = {
[user1.key]: {html: 90, css: 100},
[user2.key]: {html: 80, css: 90}
}
console.log(score[user2.key])
Symbols 在 for...in
迭代中不可枚举。另外,Object.getOwnPropertyNames()
不会返回 symbol 对象的属性,但是你能使用 Object.getOwnPropertySymbols()
得到它们。
var obj = {};
obj[Symbol("a")] = "a";
obj["c"] = "c";
obj.d = "d";
for (var i in obj) {
console.log(i); // "c" and "d"
}
注:
Symbol
声明和访问使用[]
(变量)形式操作,不能使用.
语法因为.
语法是操作字符串属性的
当使用 JSON.stringify() 时,以 symbol 值作为键的属性会被完全忽略:
js除了基本数据类型和引用数据类型外,JavaScript还提供了三种基本包装对象:number
、boolean
和string
。
let str = 'abc'
typeof str; // 'string'
let str2 = new String('abc')
typeof str; // 'object'
str === str2; // false
虽然包装对象看上去和原来的值一模一样,用typeof查看他们的类型已经变为object
了!所以,包装对象和原始值用===
比较会返回false
:
基本(原始)类型和基本包装类型的区别就是生存期不同,在代码执行后就会销毁实例。
var str = "abc";
str.age= "18";
console.log(str.age) ;// undifined第二行中给str添加了age属性,在代码执行后就会销毁,第三行再次访问的时候,age就不见了; 但通过构造函数形式是不会销毁的。
var str = new String('abc');
str.age= "18";
console.log(str.age); // '18'
但同时包装类型也具备 与各自基本类型相应的特殊行为。
实际上:每当读取一个基本类型值的时候, “后台就会创建一个 对应的基本包装类型的对象”,从而能够调用其相应原型上面的一些属性和方法来操作这些数据。
let str = 'abc';
str.length; // 3
str.toUpperCase(); // ABC
Object.prototype.toString.call
用来判断类型再合适不过,借用它我们几乎可以判断所有类型的数据:
而之前的typeof
如果用来判断数据类型,数组对象的结果都是object
类型,就无法准确区变量是数组还是对象。
// 借用Object.prototype.toString.call()获取数据类型
Object.prototype.toString.call(变量);
如判断一个变量是否是数组?
function isArray(arr){
if(Object.prototype.toString.call(arr) == '[object Array]'){
return true;
}
return false;
}
console.log( isArray(['abc']) ); // true
console.log( isArray({name:"大白"}) ); // false
或者借助es6的Array.isArray来判断
function typeOf(obj) {
const toString = Object.prototype.toString; // 获取原始的toString方法
const map = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Undefined]': 'undefined',
'[object Null]' : 'null',
'[object Object]' : 'object'
};
return map[toString.call(obj)];
}
记住一句话:this永远指向最后的调用者。
专业点说:this指向其绑定所在函数执行上下文中的this.
如果在全局中调用,函数中this一般是window,在严格模式下window为undefined。
也可以通过bind/call/apply显式改变this的指向。
在 箭头函数中,this绑定其父级上下文中的this.
var obj = {
name: "24"
getName: function(){
console.log(this)
console.log('名字' + this.name)
}
}
obj.getName(); // this 指向obj
var func = obj.getName;
func(); // this 指向 window 实质上是全局window对象在调用 ,
DOM中的this:
<body>
<button onclick="foo(this)">btnbutton>
body>
<script>
function foo(ele){
console.log(ele.innerHTML);
}
script>
一些常见的DOM操作不要忘记。
$("#box).click(function(){
console.log(this); // 代表当前绑定的标签DOM对象
// 转出jquery对象
console.log( $(this) )
})
this代表当前组件的components对
通过 bind/call/apply
语法:
函数名.bind(对象,参数1,....)
函数名.call(对象,参数1,....)
函数名.apply(对象,[参数1,....]])
三种相同点: 都可以改变函数内this的指向
不同点:
传参方式:
作用域
定义了标识符(变量名、函数名)的有限访问范围。
仅有函数才会产生局部(函数)作用域
什么是作用域链:
函数内部访问函数外部的标识符(变量名,函数名),首先会在当前作用域中查找,若找不到,则去父级作用域中找,直到找到全局,这种按链式结构查形成的结构,就是作用域链。
每个函数或对象都有__proto__属性 它的值是一个对象 即原型对象
通过构造函数创建的某个对象,当对象使用属性的时候,先在自身内存空间中进行查找,有就直接使用,若没找到就会沿__proto__属性找到其对象的原型,直到找到Object.prototype原型对象,最终属性找不到则返回undefined,方法找不到则报错。这种寻找过程形成的链式结构就是原型链
for-in继承
call/apply伪造(构造函数)继承: 去父类构造函数中伪造this,把构造函数属性添加子类对象的自身空间中。
原型继承: 继承父类原型中的属性或方法,核心还是父类的对象__proto__
找到父类的原型
子类.prototype = new 父类();
单例模式
事件性能优化-防抖节流 参考
函数防抖(debounce)和函数节流(throttle)都是为了缓解函数频繁调用,它们相似,但有区别.
函数节流:在设定的时间间隔内只会执行一次任务。如果在同一个单位时间内某事件被触发多次,只有一次能生效
函数防抖:只有任务触发的时间间隔超过设定的间隔后,任务才会执行一次。如果在这时间间隔内又被触发,则重新计时。
- 节流主要用在鼠标事件上
- 防抖主要用在键盘事件上
模块化编程(jquery库)
函数科里化
解决for循环引用问题, 及for中使用延时器setTimeout。 用let或IIFE解决。
…
概念:
柯里化,可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数
实现 add(1)(2, 3)(4)() = 10
的效果
依题意,有两个关键点要注意:
完整代码:
function currying(fn){
var allArgs = []; //闭包变量 用来接受所有的参数
return function next(){
// 将伪数组转为真数组,
// var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
if(args.length > 0){
allArgs = allArgs.concat(args); // 传参了,返回闭包函数
return next;
}else{
return fn.apply(null, allArgs); // 无参数,执行传入的函数
}
}
}
var add = currying(function(){
var sum = 0;
for(var i = 0; i < arguments.length; i++){
sum += arguments[i];
}
return sum;
});
柯里化,在这个例子中可以看出很明显的行为规范:
相关文档:
浅拷贝:浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
相关参考
通过JSON.stringify
也行,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。但是一些特殊值序列化不了,如undefined、function(){}、symbol
。
一般可以借助第三方库实现,如 lodash函数工具库的以下两个方法
_.clone(value) : 创建一个 value 的浅拷贝(只拷贝第一层)。或 Object.assign()
_.cloneDeep(value): 递归拷贝 value。(注:也叫深拷贝)。
总而言之,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
手动实现深拷贝: 通过递归实现
// 实现深拷贝:
// 主函数
function deepCopy(data){
// 只有引用类型才实现深拷贝 []、{}
if(typeof data === 'object'){
if( Array.isArray(data) ){
// 数组
return copyArray(data);
}else {
// 对象
return copyObject(data)
}
}
// 基本类型默认是深拷贝
return data;
}
// 深拷贝数组
function copyArray(arr){
// arr => [a,{},1,[]]
var newArray = []; // 相当于是一个新的空间
arr.forEach(item => {
if(typeof item === 'object'){
// 递归调用自己
newArray.push(deepCopy(item))
}else{
newArray.push(item)
}
})
return newArray;
}
// 深拷贝对象
function copyObject(obj){
// obj => {a:1,b:[]}
var newObj = {}; // 相当于是一个新的内存空间
for(let key in obj) {
if(typeof obj[key] === 'object'){ // obj {name:'age'} obj[name] => age
newObj[key] = deepCopy(obj[key])
}else{
newObj[key] = obj[key]
}
}
return newObj;
}
或:
function deepCopy(data) {
const t = typeOf(data);
let o;
if (t === 'array') {
o = [];
} else if ( t === 'object') {
o = {};
} else {
return data;
}
if (t === 'array') {
for (let i = 0; i < data.length; i++) {
o.push(deepCopy(data[i]));
}
} else if ( t === 'object') {
for (let i in data) {
o[i] = deepCopy(data[i]);
}
}
return o;
}
mdn reduce参考
arr.reduce(callback(accumulator, currentValue,[index],[array]), initialValue)
callback四个参数:
accumulator 累计器
currentValue 当前值
currentIndex 可选,当前索引
array 可选,数组
initialValue
可选,累加器函数默认值**注意:**如果没有提供
initialValue
,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue
,从索引0开始
代码示例:
var arr = [1,2,3,4];
var result = arr.reduce(function (accumulator, currentValue, index) {
// console.log(accumulator, currentValue, index)
return accumulator + currentValue;
});
console.log(result) // 10
var result = arr.reduce(function (accumulator, currentValue, index) {
// console.log(accumulator, currentValue, index)
return accumulator + currentValue;
},10);
console.log(result) // 10
reduce在mvvm中的应用,通过key获取value
什么是执行上下文:
简而言之,执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文类型:
执行上下文总共有三种类型:
全局执行上下文: 这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列操作。
执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段
Eval 函数执行上下文: 运行在 eval 函数中的代码也获得了自己的执行上下文,但由于 Javascript 开发人员不常用 eval 函数,eval函数会将传入的字符串当做 JavaScript 代码进行执行行,非常危险。所以在这里不再讨论。
console.log(eval(‘2 + 2’)); // 4
eval(‘alert(“攻击”)’)
执行上下文栈
函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那如何管理创建的那么多执行上下文呢?
JavaScript 引擎创建了执行上下文栈来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZXtcZpX-1623755833649)(http://note.youdao.com/yws/res/98/WEBRESOURCE98c81870b081d16959f62f86e62b3d01)]
从上面的流程图,我们需要记住几个关键点:
我们再来看个例子:
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();
上述代码运行按照如下步骤:
参考:
深入理解JavaScript执行上下文和执行栈
doucment object model
,文档对象模型,提供了给一套操作文档元素的api。如操作属性、类型、文本等、事件等。
Browser object model
,浏览器对象模型,提供了给一套操作浏览器的api。如前进、后退、刷新、跳转、获取屏幕分辨率,浏览器信息(navigator.userAgent,判断pc还是移动端)等jquery是一个操作页面dom元素的插件(库),常用的操作如:样式、属性、类名、文本、动画、绑定事件、发送ajax等。
$(‘css选择器’): 选择器
dom和jquery对象转换
转jquery对象: $(DOM对象)
转DOM对象: jquery对象[下标], 或者 jquery对象.get(下标)
css(): 样式操作
attr/prop: 属性操作
addClass/removeClass: 类名操作
html()/val(): 文本操作
on/bind/事件名:绑定事件。 on还可以实现事件委托 $(父元素).on('click','子元素',callback)
事件流阶段:捕获阶段-》目标阶段-》冒泡阶段
阻止事件默认行为和冒泡。
事件委托的原理:冒泡。
冒泡的原理:根据鼠标点击的xy坐标如果在父元素范围内,则会被父元素感知到,则触发父的事件
哪些事件会冒泡:有onclick、onmouseover/onmouseout
onmouseenter/onmouseleave不会冒泡
unbind/off:事件解绑
$.get/$.post/$.ajax/$.getJson
: ajax请求
$.ajax上传文件 + formData ,或者 原生ajax + formData
核心:post请求传递二进制和文本数据,设置请求头content-type为multipart/form-data
$.ajax({
type:'post',
data: new FormData(表单dom对象),
processData: false, # 代表不处理数据
contentType: false, #设置内容类型 multipart/form-data
success:function(){
}
})
$.serialize
: 序列化表单元素
什么是跨域,为什么会有跨域限制
协议、域名、端口
有不一样则就是跨域如何解决跨域
开发环境: cors、webpack(devserver proxy代理)
生产环境:cors、nginx服务器代理
原生实现需要三个事件: onmousedown、onmousemove、onmouseup
HTML5 拖拽 API
非同源受到哪些限制?
没有同源策略的危险场景
危险场景:有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进 www.yinghang.com 改密码,点击去之后网页会有类似下面代码:
iframe标签 非常危险,基本不用
如何解决跨域问题(三种)
代理
前端代理(webpack devserver.proxy)。vuecli脚手架配置devServer.proxy
nginx代理 ( nginx proxy_pass配置反向代理)
cors。服务端设置响应头允许跨域。需要服务端配合
jsonp。 jsonp不是一种技术,它是程序员 智慧的结晶
开发环境:代理proxy、cors
生产(线上)环境:nginx代理、cors
前端代理: vue.config.js
module.exports = {
// 只适用于开发环境
devServer: {
proxy: {
'/api': {
target: 'http://api.w0824.com/',
ws: true,
// changeOrigin 设置为true后,target才可以是一个域名
changeOrigin: true
},
'/wx': {
target: 'http://api.wx.com/',
ws: true,
// changeOrigin 设置为true后,target才可以是一个域名
changeOrigin: true
}
}
}
}
请求:
import axios from 'axios';
export default {
name: 'App',
methods:{
async getLunbo(){
// var apiulr = "http://api.w0824.com"
var res = await axios.get('/api/getlunbo');
console.log(res)
}
}
}
var xhr = new XMLHttpRequest ActiveXObject(‘Microsoft.XMLHTTP’)
get-4步骤, post-五部
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4 && this.status == 200){
// data '{"name":"kobe"}' '[{"name":"kobe"},{"name":"jame"}]'
var data = this.resonseText(); // data json字符串
var result = JSON.parse(data)
// 把数据做一些业务逻辑
}
}
xhr.open('post','url',true)
// 模拟post表单传递数据
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
xhr.send();
jquery-ajax $.get $.post
$.ajax()实现文件上传
原生post请求传递二进制和文本数据,核心post,设置请起头content-type为multipart/form-data
$.ajax({
type:'post',
data: new FormData(表单dom对象),
processData: false, # 代表不处理数据
contentType: false, #设置内容类型 multipart/form-data
success:function(){
}
})
get也可以实现文件上传,但要把图片文件变成base64编码即可 img src=“base64”
base64一般只需要把小图变成base64即可 webpack js .css-loader file-loader?limit=2048
Promise
window.fetch (仅浏览器支持,返回一个promise)
async/await。异步终极解决方案。 (async/await本质是生成器* yield
的语法糖形式)
var promiseArr = [];
console.time('query time')
arr.map(async (v)=>{
promiseArr.push( query(v.sql) )
})
// 并行执行多个异步请求,效率更高
var result = Promise.all(promiseArr)
console.endTime('query time')
Generator函数的强大在于**允许你通过一些实现细节来将异步过程隐藏起来,**依然使代码保持一个单线程、同步语法的代码风格。这样的语法使得我们能够很自然的方式表达我们程序的步骤/语句流程,而不需要同时去操作一些异步的语法格式。
示例1:
function* myGenerator(){
yield 1; // yield 提前返回
yield 2;
yield 3;
}
var gen = myGenerator();
// 通过next获取生成器下一次返回的值
// console.log(gen.next())
// console.log(gen.next())
// console.log(gen.next())
// console.log(gen.next())
// console.log(gen.next())
// var result;
// while(!(result = gen.next()).done){
// console.log(result.value)
// }
// for(var i of gen){
// console.log(i)
// }
示例2: 生成器和异步的结合
Generator函数的强大在于允许你通过一些实现细节来将异步过程隐藏起来,依然使代码保持一个单线程、同步语法的代码风格。这样的语法使得我们能够很自然的方式表达我们程序的步骤/语句流程,而不需要同时去操作一些异步的语法格式。
也就是说,Generator 函数相当于就是一个异步操作的容器
<script>
function* myGenerator(url) {
yield fetch(url);
}
var gen = myGenerator('http://api.w0824.com/api/getlunbo');
/*
// 或者写成函数自执行形式
var gen = (function * (url){
yield fetch(url);
})('http://api.w0824.com/api/getlunbo')
*/
var promise = gen.next().value
promise
.then(res => res.json())
.then(data => {
console.log(data)
})
script>
nodejs中可以借助axios发送异步请求:
var axios = require('axios');
console.log('1')
var gen = (function * (url){
yield axios.get(url);
})('http://api.w0824.com/api/getlunbo')
var promise = gen.next().value
promise.then(res => {
console.log(res.data)
})
console.log('3')
什么是 co 函数库
co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
co 函数库可以让你不用编写 Generator 函数的执行器(即不用写next)
var co = require('co');
co(gen);
上面代码中,Generator 函数只要传入 co 函数,就会自动执行。
co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。
co(gen).then(function (){
console.log('Generator 函数执行完成');
})
基本 使用:
var axios = require('axios');
var co = require('co')
var gen = function * (url){
var result = yield axios.get(url);
return result;
};
co(gen('http://api.w0824.com/api/getlunbo')).then(res => {
console.log(res.data)
})
async和await终极解决方案
async/await本质是 生成器* yield
的语法糖形式,书写起来更加便捷
var axios = require('axios');
var co = require('co')
var gen = async function (url){
var result = await axios.get(url);
return result;
};
co(gen('http://api.w0824.com/api/getlunbo')).then(res => {
console.log(res.data)
})
执行顺序:主线程中的同步代码执行完毕后,只剩下微任务和宏任务,顺序按照先微后宏来执行。
function test() {
console.log(1)
setTimeout(function () { // timer1
console.log(2)
}, 1000)
}
test();
setTimeout(function () { // timer2
console.log(3)
})
new Promise(function (resolve) {
console.log(4)
setTimeout(function () { // timer3
console.log(5)
}, 100)
resolve()
}).then(function () {
setTimeout(function () { // timer4
console.log(6)
}, 0)
console.log(7)
})
console.log(8)
// 结果: ?
补充关于 async/await 函数
setTimeout(() => console.log(4)) //(宏任务)
async function test() {
console.log(1); // await前: 相当于 new Promise构造函数中的同步代码
await Promise.resolve()
console.log(3); // await后: 相当于 Promise.then 中的异步代码(微任务)
}
test()
console.log(2);
// 结果 ? 1 2 3 4
推荐阅读
用过node的哪些模块,如
核心:用正确的http方法对资源(数据)进行相应的操作
常用命令:
部署一些静态资源
反向代理。如代理node服务如:3000端口
增删改查的sql:
联表join,各联表的区别
内链接:查询两个边的共同满足的数据。即两个表的交集
左联接:左边为主表,查询主表的所有数据,右表未匹配到的用null来代替
右联接:右边为主表,查询主表的所有数据,左表未匹配到的用null来代替
一些常用的优化手段
大致流程
参考:在浏览器输入 URL 回车之后发生了什么(超详细版)
对于IE来说:
只要请求的地址不发生变化,那么直接走强缓存。不会发送网络请求。状态码为 200
对于chrome和firefox
只要请求的地址不发生变化,尝试请求协商缓存(后端根据请求头标识符来判断)。 会发送网络请求,状态码为304。
相关参考:
相关参考:
http状态码:
常见状态码:
常用的请求头部(部分):
Accept: 接收类型,表示浏览器支持的MIME类型(对标服务端返回的Content-Type)
Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type:客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since:对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中
Expires:缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间
Max-age:代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中
If-None-Match:对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中
Cookie:有cookie并且同域访问时会自动带上
Connection:当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host:请求的服务器URL
Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私
Referer:该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)
User-Agent:用户客户端的一些必要信息,如UA头部等
常用的响应头部(部分):
一般来说,请求头部和响应头部是匹配分析的, 如:
请求头部的
Accept
要和响应头部的Content-Type
匹配,否则会报错。跨域请求时,请求头部的
Origin
要匹配响应头部的Access-Control-Allow-Origin
,否则会报跨域错误在使用缓存时,请求头部的
If-Modified-Since
、If-None-Match
分别和响应头部的Last-Modified
、ETag
对应
1. tcp连接三次握手的意义:
获取到服务器的IP, 三次握手才能确认双方的接收与发送能力是否正常。
简单理解:
第一次握手:由浏览器发给服务器,我想和你说话,你能 “听见” 吗?
第二次握手:由服务器发给浏览器,我能听见,你说吧!
第三次握手:由浏览器发给服务器,好,那我开始说话了。
2. tcp断开连接四次挥手的意义:
确保数据的完整性。
简单理解:
相关参考:面试官,不要再问我三次握手和四次挥手
移动端浏览器在触发点击事件的时候,通常会出现300ms左右延迟的问题
原因: 移动端的双击会缩放导致click判断延迟
解决办法:
<meta name="viewport" content="user-scalable=no" >
利用fastclick.js
插件。 参见fastclick
// jQ中使用
$(function() {
FastClick.attach(document.body);
});
// 原生中使用
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
// vue中使用
npm i fastclick
var attachFastClick = require('fastclick');
attachFastClick(document.body);
例如:一个a链接如果上层有个遮盖层,点击遮盖的touch事件会触发a链接默认行为,这就是事件穿透。
我们希望只触发遮盖的touch事件。
解决办法:
e.preventDefault();
fastclick.js
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
相关参考:
Vue的双向数据绑定原理是什么?
vue.js 是采用数据劫持结合发布者-订阅者模式(观察者模式)的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤
1、需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
2、compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
3、Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退
4、MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
观察者模式,它是一对多的一种模式。
在Vue中,一 代表什么?代表data中的某个数据
多代表什么?就是页面上凡是使用了这个数据的地方,都要更新。
就是页面上的很多”地方“,都观察着这个data,这就是一对多关系,所以用观察者模式来实现。
手写mvvm源码,实现双向绑定,实现基本的指令 v-html v-text v-model 等,
参考: 剖析Vue原理&实现双向绑定MVVM
vue如何监听对象或者数组某个属性的变化
当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。因为在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
解决方式:
this.set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)
this.set(this.arr, 0, "OBKoro1"); // 改变数组
this.$set(this.obj, "c", "OBKoro1"); // 改变对象
或者这样解决,经过vue内部处理后可以使用以下几种方法来监听数组
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
由于只针对了以上八种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
参考 Vue为什么不能检测数组变动
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
而在Vue3中取代它的Proxy有以下两个优点;
- 可以劫持整个对象,并返回一个新对象
- 有13种劫持操作
vue组件的通信方式有哪些
为什么vue组件中data必须是一个函数?
对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题
vue中v-if和v-show有什么区别?
v-if和v-show看起来似乎差不多,当条件不成立时,其所对应的标签元素都不可见,但是这两个选项是有区别的:
1、v-if在条件切换时,会对标签进行适当的创建和销毁,而v-show则仅在初始化时加载一次,因此v-if的开销相对来说会比v-show大。
2、v-if是惰性的,只有当条件为真时才会真正渲染标签;如果初始条件不为真,则v-if不会去渲染标签。v-show则无论初始条件是否成立,都会渲染标签,它仅仅做的只是简单的CSS切换
computed和watch的区别
compute计算属性:
支持缓存,只有依赖数据发生改变,才会重新进行计算
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
侦听属性watch:
侦听属性watch:
不支持缓存,数据变更,直接会触发相应的操作;watch支持异步;监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;当一个属性发生变化时,需要执行对应的操作;一对多;监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数
$nextTick是什么?
nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的 DOM
v-for 中key的作用?
当Vue用 v-for 正在更新已渲染过的元素列表是,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。key属性的类型只能为 string或者number类型。
key 的特殊属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用 key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除 key 不存在的元素
vue如何获取dom?
先给标签设置一个ref值,再通过this.$refs.domName获取,例如:
const dom = this.$refs.test
slot插槽
封装组件时候使用的,组件中的内容有时候不能写死,应该由用户决定。可以根据插槽名称控制组件的内容展示。
vue初始化页面闪动问题
使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。
Vuex
Vuex 实现组件中的数据共享。且数据是响应式的。
几个特性:
ajax请求代码应该写在组件的methods中还是vuex的actions中
如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用
vuex中的数据在页面刷新后数据消失
解决办法:用sessionstorage 或者 localstorage 存储数据。
可以引入插件vuex-persist来解决,使用方法如下:
npm install --save vuex-persist // 安装
import VuexPersistence from 'vuex-persist' // 引入
const vuexLocal = new VuexPersistence({ // 先创建一个对象并进行配置
storage: window.localStorage
})
const store = new Vuex.Store({ // 引入进vuex插件
state: { ... },
mutations: { ... },
actions: { ... },
plugins: [vuexLocal.plugin]
})
通过以上设置,如果刷新某个视图,数据并不会丢失,依然存在,并且不需要在每个 mutations 中手动存取 storage 。
怎么在组件中批量使用Vuex的getter属性
使用mapGetters辅助函数, 利用对象展开运算符将getter混入computed 对象中
import {mapGetters} from 'vuex'
export default{
computed:{
...mapGetters(['total','discountTotal'])
}
}
组件中重复使用mutation
import { mapMutations } from 'vuex'
methods:{
...mapMutations({
setNumber:'SET_NUMBER',
})
}
然后调用this.setNumber(10)相当调用this.$store.commit(‘SET_NUMBER’,10)
mutation和action有什么区别
action 提交的是 mutation,而不是直接变更状态。mutation可以直接变更状态
action 可以包含任意异步操作。mutation只能是同步操作
提交方式不同:
action 是用this.store.dispatch('ACTION_NAME',data)来提交。
mutation是用this.$store.commit('SET_NUMBER',10)来提交
在v-model上怎么用Vuex中state的值?
// ...
computed: {
message: {
get () {
return this.$store.state.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
前端路由:
前端路由是现代SPA(单页应用)应用必备的功能,每个现代前端框架都有对应的实现,例如vue-router、react-router
hash路由一个明显的标志是带有#
,我们主要是通过监听url中的hash变化来进行路由跳转。
多页面应用(MPA)
多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整页刷新。
单页面的优点:
单页面缺点:
由于hash路由的优势就是兼容性更好,在老版IE中都有运行,问题在于url中一直存在#
不够美观,而且hash路由更像是Hack而非标准,相信随着发展更加标准化的History API会逐步蚕食掉hash路由的市场。
router和route的区别
route为当前router跳转对象里面可以获取name、path、query、params等
router为VueRouter实例,想要导航到不同URL,则使用router.push方法
路由按需加载
随着项目功能模块的增加,引入的文件数量剧增。如果不做任何处理,那么首屏加载会相当的缓慢,这个时候,路由按需加载就闪亮登场了。
{
path:'/',
name:'home',
components:()=>import('@/components/home')
}
import()方法是由es6提出的,动态加载返回一个Promise对象,then方法的参数是加载到的模块。类似于Node.js的require方法,主要import()方法是异步加载的。
axios封装网络请求
参考下 Vue 中 Axios 的封装和 API 接口的管理 即可
核心是:
也可以封装独立的get或post请求
vue常用ui库
移动端h5: vantui、iview
pc端: element-ui 、Ant Design of Vue
常用webpack配置
如:devServer 选项中配置proxy代理跨域
devServer: {
//配置开发服务器
host: "0.0.0.0",
//是否启用热加载,就是每次更新代码,是否需要重新刷新浏览器才能看到新代码效果
hot: true,
//服务启动端口
port: "8080",
//是否自动打开浏览器默认为false
open: false,
//配置http代理
proxy: {
"/api": { //如果ajax请求的地址是http://192.168.0.118:9999/api1那么你就可以在jajx中使用/api/api1路径,其请求路径会解析
// http://192.168.0.118:9999/api1,当然你在浏览器上开到的还是http://localhost:8080/api/api1;
target: "http://192.168.0.118:9999",
//是否允许跨域,这里是在开发环境会起作用,但在生产环境下,还是由后台去处理,所以不必太在意
changeOrigin: true,
pathRewrite: {
//把多余的路径置为''
"api": ""
}
},
"/api2": {//可以配置多个代理,匹配上那个就使用哪种解析方式
target: "http://api2",
// ...
}
}
}
参考:常用webpack配置
史上最强vue总结—面试开发全靠它了
前端大文件上传
剖析Vue原理&实现双向绑定MVVM
vue3.0 尝鲜 – 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索
Vue.js 3.0 新特性预览
[译]一篇帮你彻底弄懂NodeJs中的Buffer 原文