问题1:区别array和object
这里考的是判断数据类型的方法
常用的几种数据类型判断方法如下:typeof、instanceof、constructor、Object.prototype.toString.call()
typeof(可以对基本类型做出准确的判断,对于引用类型,就有点力不从心了)
typeof 返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、object、undefined、function、Symbol6种数据类型。
对于引用类型,返回的都是object,其实返回object也没有错,因为所有对象的原型链最终都指向了Object。
instanceof
判断对象和构造函数在原型链上是否有关系,如果有关系,返回真,否则返回假
constructor:查看对象对应的构造函数
注:undefined和null是不能够判断出类型的,并且会报错。因为null和undefined是无效的对象,因此是不会有constructor存在的
使用constructor是不保险的,因为constructor属性是可以被修改的,会导致检测出的结果不正确
Object.prototype.toString(不管是什么类型,都可以判断出)
问题2:es6 class怎么设置原型、静态、实例方法
类相当于实例的原型, 所有在类中定义的方法, 都会被实例继承。 如果在一个方法前, 加上static关键字, 就表示该方法不会被实例继承, 而是直接通过类来调用, 这就称为“ 静态方法”。
静态属性指的是 Class 本身的属性, 即Class.propname, 而不是定义在实例对象( this) 上的属性。
类的实例属性就是在类实例中添加属性,可以用等式, 写入类的定义之中。
问题3:经常使用到的array方法,类数组怎么转换为数组
常用的array方法
类数组怎么转换为数组
类数组:一个简单的定义,如果一个对象有 length
属性值,则它就是类数组
- 遍历类数组,依次将元素放入一个空数组。
类数组本身虽然不是数组,但是有interator接口,所以可遍历。(interator指可遍历、可迭代) - 用扩展运算符。
扩展运算符的使用前提,是对象有Interator接口,这和let of的前提是一样的。 - Array.from()方法转换
Array.from()是ES6中新增的方法,可以将两类对象转为真正的数组:类数组对象和可遍历(iterable)对象(包括ES6新增的数据结构Set和Map)。
问题4:DOM怎么添加事件
直接在标签上添加
<button onclick="function()">按钮</button>
获取元素动态添加
<input id="demo" type="button" value="" />
<script type="text/javascript">
document.getElementById("demo").onclick=function(){
alert(this.getAttribute("type"));
}
</script>
addEventListener:添加事件监听
document.getElementById(元素id).addEventListener("click", function(){
console.log("目标元素被点击了");
});
问题5:cookie、localstorage的区别,哪些情况和设置,请求不会携带cookie
cookie、localstorage的区别
哪些情况和设置,请求不会携带cookie
ajax请求默认情况下不会携带cookie, 只有当设置了 credentials
时才会带上与请求同域的cookie, 并且服务端需要设置响应头 Access-Control-Allow-Credentials: true
, 否则浏览器会报错, 拿不到响应
axios 默认情况下, 请求同域ajax会带上cookie, 跨域ajax不带cookie
如果满足下面几个条件:
- 浏览器端某个 Cookie 的 domain(.a.com) 字段等于请求的域名或者是请求的父域名,请求的域名需要是 a.com/b.a.com 才可以
- 都是 http 或者 https,或者不同的情况下 Secure 属性为 false(即 secure 是 true 的情况下,只有 https 请求才能携带这个 cookie)
- 要发送请求的路径,跟浏览器端 Cookie 的 path 属性必须一致,或者是浏览器端 Cookie 的 path 的子目录,比如浏览器端 Cookie 的 path 为 /test,那么请求的路径必须为/test 或者/test/xxxx 等子目录才可以
上面 3 个条件必须同时满足,否则该请求就不能自动带上浏览器端已存在的 Cookie
问题6:nextTick的原因和作用,项目哪些场景用到了nextTick
vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick就可以获取数据更新后最新DOM的变化。总结下:nextTick 就是获取渲染后的数据
。
用在哪里:在修改数据,需要获取到最新的DOM数据的时候会使用。 比如从服务器拉数据渲染后希望拿到dom的高度可以在nextTick 里获取。
问题7:js函数的4种调用方式
函数的名字只是一个指向函数的指针,所以即使在不同的执行环境,即不同对象调用这个函数,这个函数指向的仍然是同一个函数。
四种调用函数的方式:函数调用模式、方法调用模式、构造器模式、apply\call模式.
1、直接调用函数的方式,this指向的全局对象window.
function add(a,b) {
return a+b;
}
console.log(add(1,2));
2、函数作为对象的方法调用,this指向当前的对象.
var o = {
prop: 21,
f:function() {
return this.prop;
}
};
console.log(o.f());
3、通过new调用构造器的方式,this指向当前构造函数的原型.
function MyClass() {
this.a = 21;
}
var o = new MyClass();
console.log(o.a);
4、通过apply或call的方式,这两个的第一参数即this,当第一个参数为null,this指向window;当第一个参数为一个对象,this就指向当前这个对象。
总结一下:这些模式中,this 的含义分别为:在函数中 this 是全局对象 window,在方法中 this 指当前对象,在构造函数中 this 是被创建的对象,在 apply 模式中 this 可以随意的指定. 在 apply 模式中如果使用 null,就是函数模式,如果使用对象,就是方法模式.
问题8:regexp正则
RegExp 对象表示正则表达式,它是对字符串执行模式匹配的强大工具。
创建正则有两种方法:
构造函数创建
var 变量 = new RegExp("正则表达式"); // 注意,参数是字符串
使用字面量创建
var 变量 = /正则表达式/; // 注意,这个语法里没有引号
正则的方法
1、 判断字符串是否符合规则 test()
var reg = /[^ab]/; // 规则:字符串中,除了a、b之外,还有没有其他的字符内容?
var str = "acb";
console.log(reg.test(str)); // 打印结果:true
2、搜索第一个符合条件的字符串 search()
var str = "hello abc hello aec afc";
// 搜索字符串中是否含有abc 或 aec 或 afc
result = str.search(/a[bef]c/);
console.log(result); // 打印结果:6
3、搜索所有符合条件的字符串match()
g是全局匹配模式,i是忽略大小写
var str = "1a2a3a4a5e6f7A8B9C";
var result1 = str.match(/[a-z]/); // 找到符合要求的第一个内容,然后返回
var result2 = str.match(/[a-z]/g); // 设置为“全局匹配”模式,匹配字符串中 所有的小写字母
var result3 = str.match(/[a-z]/gi); // 设置多个匹配模式,匹配字符串中 所有的字母(忽略大小写)
console.log(result1); // 打印结果:["a"]
console.log(result2); // 打印结果:["a", "a", "a", "a", "e", "f"]
console.log(result3); // 打印结果:["a", "a", "a", "a", "e", "f", "A", "B", "C"]
4、字符串拆分成数组 split()
var str = "1a2b3c4d5e6f7g";
var result = str.split(/[A-z]/); // 参数是一个正则表达式:表示所有字母
console.log(result); // ["1", "2", "3", "4", "5", "6", "7", ""]
5、字符串替换 replace()
let str1 = '2020/5/20'
console.log(str1.replace(/\//g,"-")); //得到 2020-5-20
var str2 = "Today is fine day,today is fine day !!!"
console.log(str2.replace("today","tomorrow")); //只能替换第一个today
console.log(str2.replace(/today/gi,"tomorrow")); //这里用到了正则,且为“全局匹配”模式,才
能替换所有的today
问题9:DNS
DNS(Domain Names System),域名系统,是互联网一项服务,是进行域名和与之相对应的 IP 地址进行转换的服务器
简单来讲,DNS
相当于一个翻译官,负责将域名翻译成ip
地址
DNS缓存
浏览器缓存:浏览器在获取网站域名的实际 IP 地址后会对其进行缓存,减少网络请求的损耗
操作系统缓存:操作系统的缓存其实是用户自己配置的 hosts
文件
DNS解析
-
首先搜索浏览器自身的DNS缓存,如果存在,则域名解析到此完成。
-
如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的hosts文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
-
如果本地hosts文件不存在映射关系,则查找本地DNS服务器如果存在,域名到此解析完成。
-
如果本地DNS服务器还没找到的话,它就会向根服务器发出请求,进行递归查询。
问题10:CDN
简单的理解CDN就是缓存服务器的承包商,帮助他们在最近的CDN节点用最短的请求时间拿到资源,起到分流作用,减轻服务器负载压力。
用户在浏览网站的时候,CDN会选择一个离用户最近的CDN边缘节点来响应用户的请求,这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器(假设源站部署在北京电信机房)上了。
当浏览器向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向服务器发出回源请求,从服务器拉取最新数据,更新本地缓存,并将最新数据返回给客户端。
CDN优点
1. CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低。
2. CDN起到了分流作用,减轻了源服务器的负载。
问题11:script会堵塞html吗
这属于 js同步和异步的问题
解析 HTML 过程中遇到 普通外联 scripts
会暂停解析,发送请求并执行 scripts,然后继续解析 HTML。
但是在script中加入async 和 defer 属性,就会采用并行下载,在下载的过程中不会产生阻塞。
两种属性用于异步加载脚本,区别在于执行时机,async 是加载完成后立刻执行,defer 需要等待页面完成后执行。
同时存在defer和async,那么defer的优先级比较高,脚本将在页面完成时执行。
问题12:CSS单行居中,多行向左
CSS部分
<style>
.content {
text-align: center;
}
.text {
display: inline-block; //宽度由内部元素决定,但永远小于“包含块”(content)容器的尺寸
text-align: left;
}
</style>
html部分
<div class="content">
<span class="text">这段文字能不能这样判断一下,当文字不足一行时,让它居中显示,当文字超过一
行就让它居左</span>
</div>
问题13:vuex的属性和方法
vuex有五个核心概念:state, getters, mutations, actions, modules。
1、state:vuex的基本数据,用来存储变量
-
getter:从基本数据(state)派生的数据,相当于state的计算属性,具有返回值的方法
-
mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。提交mutation是更改Vuex中的store中的状态的唯一方法。 -
action:和mutation的功能大致相同,不同之处在于
-
Action 提交的是 mutation,而不是直接变更状态。
-
Action 可以包含任意异步操作。Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
-
modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
混入对象mixin用法及其与Vuex和公共组件的区别
Vuex
中的所有变量和方法都是可以读取和更改并相互影响的。
mixin
中的数据是不共享的,也就是每个组件中的mixin
实例都是不一样的,都是单独存在的个体,不存在相互影响的
问题14:promise是同步还是异步,then方法是同步还是异步
promise构造函数是同步执行的,then方法是异步执行的
new Promise(resolve=>{
console.log(1);
resolve(3);
}).then(num=>{
console.log(num);
});
console.log(2);
//依次为 123
resolve和reject函数被调用时,分别将promise的状态改为fulfilled(完成)或者rejected(失败).一旦状态改变,就不会再变。
构造函数里面的resolve与reject函数是直接执行的,但是在then方法里面执行的成功或失败回调函数都绑定了定时器,所以是异步的。
问题15:key的作用
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较。
原理(比较规则):
1.旧虚拟DOM中找到了与新虚拟DOM相同的key:
a.若虚拟DOM中内容没变,直接使用之前的直DOM!
b.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
2.旧虚拟DOM中未找到与新虚拟DOM相同的key:
a.创建新的真实DOM,随后渲染到到页面。
问题16:遍历数组的方法中有哪些是不能中断的
map:只能遍历数组,不能中断,返回值是修改后的数组
let arr = [1,2,3];
const res = arr.map(item => {
return item + 1;
})
console.log(res); // [2,3,4]
console.log(arr); // [1,2,3]
问题17:项目中token/cookie,放在哪里
token 在客户端一般存放于localStorage、cookie、或sessionStorage中。
token存储在cookie中,可以制定httponly,来防止被JavaScript读取
问题18:TCP 为什么是安全的
问题19:为什么握手是三次 挥手是四次
三次握手
三次握手的目的建立可靠的通信信道。通俗来说是确认双方的发送和接收正常。
第一次握手(客户端发送标有SYN的数据包):客户端什么都不能确认;服务端确认了对方发送正常,自己接收正常。
第二次握手(服务端发送标有SYN/ACK的数据包):客户端确认自己发送、接收正常,对方发送、接收正常;服务端确认对方发送正常,自己接收正常。
第三次握手(客户端发送标有ACK的数据包给服务端):客户端确认自己发送、接收正常,对方发送、接收正常;服务端确认自己发送、接收正常,对方发送、接收正常。
四次挥手
当双方都确认对方都没有数据要发送的时候,才能够完全断开TCP连接。
第一次挥手(客户端发送一个FIN到服务器):客户端用来告知服务端自己要关闭数据传送
第二次挥手(服务端接收到FIN,并返回一个ACK,确认序号为收到的序号加1):服务端告诉客户端我收到了
第三次挥手(服务端关闭客户端的连接,发送一个FIN给客户端):服务端告诉客户端我也没有话要说了准备关闭
第四次挥手(客户端发回ACK报文确认,并将确认序号设置为收到序号加1):客户端告知服务端我收到了,然后完全关闭TCP连接。
问题20:babel是怎么编译的
首先要了解的是,Babel
是一个 JavaScript
编译器。有什么作用呢?
1. Babel 被用来转译 ECMAScript 2015+ 至可兼容浏览器的版本
2. React中使用了高级jsx
语法的时候。
注:JSX=javascript xml就是Javascript和XML结合的一种格式。是 JavaScript 的语法扩展,只要你把HTML代码写在JS里,那就是JSX。
Babel 的编译过程和大多数其他语言的编译器相似,可以分为三个阶段:
- 解析(Parsing):将代码字符串解析成抽象语法树。
- 转换(Transformation):对抽象语法树进行转换操作。
- 生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串。
问题21:loader和plugin的区别
- loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事
可以看到,两者在运行时机上的区别:
- loader 运行在打包文件之前
- plugins 在整个编译周期都起作用
在Webpack
运行的生命周期中会广播出许多事件,Plugin
可以监听这些事件,在合适的时机通过Webpack
提供的 API
改变输出结果
对于loader
,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss
或A.less
转变为B.css
,单纯的文件转换过程。
问题22:promise.all和.race
promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = Promse.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败了,打出 '失败'
})
promise.race
顾名思义,race就是赛跑的意思,用在promise中可以这样理解为,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
附一道牛客中刷到的题
Promise.all([]).then((res) => {
console.log('all');
});
Promise.race([]).then((res) => {
console.log('race');
});
输出的结果是什么呢?
仅输出all,不输出race。
all允许空数组,race不允许空数组。Promise.all([ ])中,数组为空数组,则立即决议为成功执行resolve( );Promise.race([ ])中数组为空数组,就不会执行。
问题23:let const编译成var怎么实现块级作用域,怎么判断它声明的是否是全局的呢
块级作用域
作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗的讲就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和声明周期
在ES6之前,只有两种作用域:全局作用域和函数作用域
没有块级作用域会导致很多问题:
- 变量覆盖
- 命名冲突
- 变量提升
- 内存泄漏
在编译阶段:
使用var声明的变量会进入变量环境,而使用const/let声明的变量会进入词法环境
作用域块中通过const/let
声明的变量,会被放在词法环境的一个单独区域中,这个区域中的变量并不影响作用域块外面的变量,比如在作用域外面声明了变量 b,在该作用域块内部也声明了变量 b,当执行到作用域内部时,它们都是独立的存在。
判断全局变量
首先,判断这个变量是否在函数内定义,如果未定义却能使用为全局变量;
其次,观察变量的定义位置,如果不属于任何一个函数,则为全局变量。
问题24:请求拦截器,响应拦截器的使用
- 请求拦截器
在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装; - 响应拦截器
同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等。
问题25:hasOwnProperty属性,可以读取到原型上的属性吗?
hasOwnProperty (propertyName) 方法是用来检测属性是否为对象的自有属性,如果是,返回true,否者false; 参数propertyName指要检测的属性名;
hasOwnProperty() 只会检查对象的自有属性,对象原型上的属性其不会检测;
但是对于原型对象本身来说,这些原型上的属性又是原型对象的自有属性,所以原形对象也可以使用hasOwnProperty()检测自己的自有属性。
问题26:了解下reduce
reduce(callback,initiaValue)会传入两个变量,回调函数(callback)和初始值(initiaValue)。
假设函数有4个传入参数,prev和next,index和array。 Prev和next是必须要了解的。
当没有传入初始值时,prev是从数组中第一个元素开始的,next数组是第二个元素。
但是当传入初始值(initiaValue)后,第一个prev将是initivalValue,next将是数组中的第一个元素。
简单来说就是对一个array
执行reduce()
方法,就是把其中的function()
挨个地作用于array
中的元素上,而且上一次的输出会作为下一次的一个输入。
1. 计算数组总和
var num = [1.5, 2, 3, 4, 5];
var res = num.reduce(function (total, num) {
return total + num;
}, 0); //0 就是initiaValue
console.log(res) // 15.5
2. 合并二维数组
var arr = [[0, 1], [2, 3], [4, 5]].reduce(function (a, b) {
return a.concat(b);
}, []); //[] 就是initiaValue
console.log(arr) //[0, 1, 2, 3, 4, 5]
reduce有许多用的地方,上面两个只是举个例子。核心就是上一次的输出会作为下一次的输入。记住
(callback,initiaValue)。
问题27:Vue路由传参两种方式区别,怎么获取参数
vue路由传参是指嵌套路由时父路由向子路由传递参数,否则操作无效。传参方式可以划分为params传参和query传参
-
query可以使用path或者name来进行携带跳转地址,params只能使用name携带跳转地址,即push里面只能是name:’xxxx’,不能是path:’/xxx’,。
-
params是路由的一部分,必须要在路由后面添加参数名。使用params不在跳转地址上配参数跳转,只有第一次进入页面参数有效,刷新页面参数就会消失。 query是拼接在url后面的参数,没有也没关系。
问题28:route和router的区别
$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。
问题29:路由跳转的方式有哪些,区别是什么
1. 声明式导航router-link
(1)不带参数
<router-link :to="{name:'home'}">
<router-link :to="{path:'/home'}">
(2)带参数
<router-link :to="{name:'home,params:{id:1}'}">
2. 编程式导航 this.$router.push() (函数里面调用)()
(1)不带参数
this.$router.push('/home')
this.$router.push({name:'home'})
this.$router.push({path:'/home'})
(2)带参数
(1)query传参
this.$router.push({path:'/home',query: {id:'1'}})
//script 取参 this.$route.query.id
(2)params传参
this.$router.push({name:'home',params: {id:'1'}})
// 只能用 name
// script 取参 this.$route.params.id
区别
1.声明式导航直接写在html中,结构简单使用方便,但是只能放在<router-link>
标签中使用,<router-link>
标签会将路由转成<a>
标签,通过点击跳转路由,因此局限性也非常大。
2.编程式导航则是用在js处理逻辑后需要页面跳转,比如点击button按钮跳转。
问题30:组件传参
组件传参可以分为 1、父传子 ; 2、子传父;3、兄弟传;4;无关系传。
父传子(自定义属性 - props)
父组件向子组件传参,通过自定义属性的方式进行传参,在子组件中使用prop定义自定义的属性,然后在父组件中通过v-bind指令把需要传递的数据绑定在子组件上,那在子组件中props里面的自定义属性可以直接使用.
//父组件代码 渲染子组件
<Son :sonName="uname" />
// 子组件代码,接受父参数
export default {
props: {
sonName: String
}
}
子传父(自定义事件 - this.$emit)
子组件向父组件传数据使用自定义事件, vue组件提供了一个emit方法, 使用方法 this.$emit('自定义事件名', 传递的数据)
,子组件传递数据时,触发响应的事件函数,就会自动触发通过$emit
给父组件绑定的自定义事件,自定义事件接受一个参数,参数就是子组件传递过来的数据
// 父组件代码,渲染子组件
<Son @pass-value="valueFn" />
export default{
method:{
valueFn(value) {}
}
}
// 子组件代码
this.$emit('pass-value', this.say)
兄弟组件
兄弟组件之间的数据传递,通过eventBus来做中间的桥梁,传输方通过中间组件调用 emit 传数据,接收方通过on 接受数据,两者之间的自定义属性名保持一致。
// 传输方组件调用方式
import Bus from '@/EventBus.js'
Bus.$emit('pass-value', this.say);
// 接收方接受参数
import Bus from '@/EventBus.js'
created() {
Bus.$on('pass-value', val => {
this.sibilingValue = val;
})
}
Vuex数据共享
通过vuex存储数据, Vuex是一个专为vue.js 应用程序开发的状态管理模式, 它采用集中式存储管理数据,以相应的规则保证状态以一种可预测的方式发生改变, 一变全变,同步更新数据。
// 注册代码
const store = new Vue.Store({
state:{
count: 100
},
mutations: {
addCount(state, val = 1) {
state.count += val;
},
subCount(state, val = 1) {
state.count -= val;
}
}
})
// 组件调用
this.$store.commit('addCount'); // 加 1
this.$store.commit('subCount'); // 减 1
问题31:scope的作用是什么,怎么实现的
作用:实现组件的私有化,不对全局造成样式污染,表示当前style属性只属于当前模块。
原理:scoped加上唯一性的标记【data-v-something】属性,即CSS带属性选择器,以此完成类似作用域的选择方式,从而达到样式私有化,不污染全局的作用。