说下对 JS 的了解吧
是基于原型的动态语言,主要独特特性有 this、原型和原型链。
JS 严格意义上来说分为:语言标准部分(ECMAScript)+ 宿主环境部分
语言标准部分
2015 年发布 ES6,引入诸多新特性使得能够编写大型项目变成可能,标准自 2015 之后以年号代号,每年一更
宿主环境部分
说一下 SPA 单页面有什么优缺点?
优点:
1.体验好,不刷新,减少 请求 数据ajax异步获取 页面流程;
2.前后端分离
3.减轻服务端压力
4.共用一套后端程序代码,适配多端
缺点:
1.首屏加载过慢;
2.SEO 不利于搜索引擎抓取
复制代码
为什么有时候⽤translate 来改变位置⽽不是定位?
translate 是 transform 属性的⼀个值。改变 transform 或 opacity 不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发复合(compositions)。⽽改变绝对定位会触发重新布局,进⽽触发重绘和复合。transform 使浏览器为元素创建⼀个 GPU 图层,但改变绝对定位会使⽤到 CPU。 因此 translate()更⾼效,可以缩短平滑动画的绘制时间。 ⽽translate 改变位置时,元素依然会占据其原始空间,绝对定位就不会发⽣这种情况。
代码输出结果
var a = 1;
function printA(){
console.log(this.a);
}
var obj={
a:2,
foo:printA,
bar:function(){
printA();
}
}
obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1
复制代码
输出结果: 2 1 1
解析:
obj.foo(),foo 的 this 指向 obj 对象,所以 a 会输出 2;
obj.bar(),printA 在 bar 方法中执行,所以此时 printA 的 this 指向的是 window,所以会输出 1;
foo(),foo 是在全局对象中执行的,所以其 this 指向的是 window,所以会输出 1;
代码输出结果
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
复制代码
输出结果如下:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
复制代码
这里其实是一个坑,.then
或 .catch
返回的值不能是 promise 本身,否则会造成死循环。
代码输出结果
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
复制代码
输出结果如下:
看到这个题目,好多的 then,实际上只需要记住一个原则:.then
或.catch
的参数期望是函数,传入非函数则会发生值透传。
第一个 then 和第二个 then 中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将resolve(1)
的值直接传到最后一个 then 里,直接打印出 1。
参考 前端进阶面试题详细解答
代码输出结果
var friendName = 'World';
(function() {
if (typeof friendName === 'undefined') {
var friendName = 'Jack';
console.log('Goodbye ' + friendName);
} else {
console.log('Hello ' + friendName);
}
})();
复制代码
输出结果:Goodbye Jack
我们知道,在 JavaScript 中, Function 和 var 都会被提升(变量提升),所以上面的代码就相当于:
var name = 'World!';
(function () {
var name;
if (typeof name === 'undefined') {
name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
复制代码
这样,答案就一目了然了。
代码输出结果
var a = 10;
var obt = {
a: 20,
fn: function(){
var a = 30;
console.log(this.a)
}
}
obt.fn(); // 20
obt.fn.call(); // 10
(obt.fn)(); // 20
复制代码
输出结果: 20 10 20
解析:
obt.fn(),fn 是由 obt 调用的,所以其 this 指向 obt 对象,会打印出 20;
obt.fn.call(),这里 call 的参数啥都没写,就表示 null,我们知道如果 call 的参数为 undefined 或 null,那么 this 就会指向全局对象 this,所以会打印出 10;
(obt.fn)(), 这里给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20;
说一下常见的 git 操作
git branch 查看本地所有分支
git status 查看当前状态
git commit 提交
git branch -a 查看所有的分支
git branch -r 查看远程所有分支
git commit -am "nit" 提交并且加注释
git remote add origin git@192.168.1.119:ndshow
git push origin master 将文件给推到服务器上
git remote show origin 显示远程库origin里的资源
git push origin master:develop
git push origin master:hb-dev 将本地库与服务器上的库进行关联
git checkout --track origin/dev 切换到远程dev分支
git branch -D master develop 删除本地库develop
git checkout -b dev 建立一个新的本地分支dev
git merge origin/dev 将分支dev与当前分支进行合并
git checkout dev 切换到本地dev分支
git remote show 查看远程库
git add .
git rm 文件名(包括路径) 从git中删除指定文件
git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来
git config --list 看所有用户
git ls-files 看已经被提交的
git rm [file name] 删除一个文件
git commit -a 提交当前repos的所有的改变
git add [file name] 添加一个文件到git index
git commit -v 当你用-v参数的时候可以看commit的差异
git commit -m "This is the message describing the commit" 添加commit信息
git commit -a -a是代表add,把所有的change加到git index里然后再commit
git commit -a -v 一般提交命令
git log 看你commit的日志
git diff 查看尚未暂存的更新
git rm a.a 移除文件(从暂存区和工作区中删除)
git rm --cached a.a 移除文件(只从暂存区中删除)
git commit -m "remove" 移除文件(从Git中删除)
git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)
git diff --cached 或 $ git diff --staged 查看尚未提交的更新
git stash push 将文件给push到一个临时空间中
git stash pop 将文件从临时空间pop下来
复制代码
vue-router
vue-router是vuex.js官方的路由管理器,它和vue.js的核心深度集成,让构建但页面应用变得易如反掌
<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址
<router-view> 组件是一个 functional 组件,渲染路径匹配到的视图组件。
<keep-alive> 组件是一个用来缓存组件
router.beforeEach
router.afterEach
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
介绍了路由守卫及用法,在项目中路由守卫起到的作用等等
复制代码
setInterval 模拟 setTimeout
描述:使用setInterval
模拟实现setTimeout
的功能。
思路:setTimeout
的特性是在指定的时间内只执行一次,我们只要在setInterval
内部执行 callback
之后,把定时器关掉即可。
实现:
const mySetTimeout = (fn, time) => {
let timer = null;
timer = setInterval(() => {
// 关闭定时器,保证只执行一次fn,也就达到了setTimeout的效果了
clearInterval(timer);
fn();
}, time);
// 返回用于关闭定时器的方法
return () => clearInterval(timer);
}
// 测试
const cancel = mySetTimeout(() => {
console.log(1);
}, 1000);
// 一秒后打印 1
复制代码
代码输出结果
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
复制代码
输出结果如下:
我们知道,.then
函数中的两个参数:
第一个参数是用来处理 Promise 成功的函数
第二个则是处理失败的函数
也就是说Promise.resolve('1')
的值会进入成功的函数,Promise.reject('2')
的值会进入失败的函数。
在这道题中,错误直接被then
的第二个参数捕获了,所以就不会被catch
捕获了,输出结果为:error err!!!'
但是,如果是像下面这样:
Promise.resolve()
.then(function success (res) {
throw new Error('error!!!')
}, function fail1 (err) {
console.log('fail1', err)
}).catch(function fail2 (err) {
console.log('fail2', err)
})
复制代码
在then
的第一参数中抛出了错误,那么他就不会被第二个参数不活了,而是被后面的catch
捕获到。
Vue 路由守卫有哪些,怎么设置,使用场景等
常用的两个路由守卫:router.beforeEach 和 router.afterEach
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。
在项目中,一般在beforeEach这个钩子函数中进行路由跳转的一些信息判断。
判断是否登录,是否拿到对应的路由权限等等。
复制代码
代码输出结果
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
复制代码
输出结果如下:
(1)第一轮事件循环流程分析如下:
整体 script 作为第一个宏任务进入主线程,遇到console.log
,输出 1。
遇到setTimeout
,其回调函数被分发到宏任务 Event Queue 中。暂且记为setTimeout1
。
遇到process.nextTick()
,其回调函数被分发到微任务 Event Queue 中。记为process1
。
遇到Promise
,new Promise
直接执行,输出 7。then
被分发到微任务 Event Queue 中。记为then1
。
又遇到了setTimeout
,其回调函数被分发到宏任务 Event Queue 中,记为setTimeout2
。
上表是第一轮事件循环宏任务结束时各 Event Queue 的情况,此时已经输出了 1 和 7。发现了process1
和then1
两个微任务:
执行process1
,输出 6。
执行then1
,输出 8。
第一轮事件循环正式结束,这一轮的结果是输出 1,7,6,8。
(2)第二轮时间循环从**setTimeout1**
宏任务开始:
第二轮事件循环宏任务结束,发现有process2
和then2
两个微任务可以执行:
第二轮事件循环结束,第二轮输出 2,4,3,5。
(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。
第三轮事件循环宏任务执行结束,执行两个微任务process3
和then3
:
第三轮事件循环结束,第三轮输出 9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为 1,7,6,8,2,4,3,5,9,11,10,12。
代码输出结果
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise(resolve => {
console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {
console.log(5)
new Promise(resolve => {
resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {
console.log(7)
})
console.log(8)
复制代码
输出结果如下:
代码执行过程如下:
首先执行 script 代码,打印出 1;
遇到第一个定时器,加入到宏任务队列;
遇到 Promise,执行代码,打印出 3,遇到 resolve,将其加入到微任务队列;
遇到第二个定时器,加入到宏任务队列;
遇到第三个定时器,加入到宏任务队列;
继续执行 script 代码,打印出 8,第一轮执行结束;
执行微任务队列,打印出第一个 Promise 的 resolve 结果:4;
开始执行宏任务队列,执行第一个定时器,打印出 2;
此时没有微任务,继续执行宏任务中的第二个定时器,首先打印出 5,遇到 Promise,首选打印出 6,遇到 resolve,将其加入到微任务队列;
执行微任务队列,打印出 6;
执行宏任务队列中的最后一个定时器,打印出 7。
代码输出结果
function foo(something){
this.a = something
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(2);
console.log(obj1.a); // 2
obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3
var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4
复制代码
输出结果: 2 3 2 4
解析:
首先执行 obj1.foo(2); 会在 obj 中添加 a 属性,其值为 2。之后执行 obj1.a,a 是右 obj1 调用的,所以 this 指向 obj,打印出 2;
执行 obj1.foo.call(obj2, 3) 时,会将 foo 的 this 指向 obj2,后面就和上面一样了,所以会打印出 3;
obj1.a 会打印出 2;
最后就是考察 this 绑定的优先级了,new 绑定是比隐式绑定优先级高,所以会输出 4。
代码输出结果
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function() {
console.log(this.foo);
console.log(self.foo);
}());
}
};
myObject.func();
复制代码
输出结果:bar bar undefined bar
解析:
首先 func 是由 myObject 调用的,this 指向 myObject。又因为 var self = this;所以 self 指向 myObject。
这个立即执行匿名函数表达式是由 window 调用的,this 指向 window 。立即执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。
说一说什么是跨域,怎么解决
因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。
为来防止CSRF攻击
1.JSONP
JSONP 的原理很简单,就是利用 <script> 标签没有跨域限制的漏洞。 通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。 <script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) { console.log(data) } </script>
JSONP 使用简单且兼容性不错,但是只限于 get 请求。
2.CORS
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
3.document.domain
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域
4.webpack配置proxyTable设置开发环境跨域
5.nginx代理跨域
6.iframe跨域
7.postMessage
这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
复制代码
代码输出结果
function a(xx){
this.x = xx;
return this
};
var x = a(5);
var y = a(6);
console.log(x.x) // undefined
console.log(y.x) // 6
复制代码
输出结果: undefined 6
解析:
最关键的就是 var x = a(5),函数 a 是在全局作用域调用,所以函数内部的 this 指向 window 对象。所以 this.x = 5 就相当于:window.x = 5。之后 return this,也就是说 var x = a(5) 中的 x 变量的值是 window,这里的 x 将函数内部的 x 的值覆盖了。然后执行 console.log(x.x), 也就是 console.log(window.x),而 window 对象中没有 x 属性,所以会输出 undefined。
当指向 y.x 时,会给全局变量中的 x 赋值为 6,所以会打印出 6。
虚拟 DOM 转换成真实 DOM
描述:将如下 JSON 格式的虚拟 DOM 结构转换成真实 DOM 结构。
// vnode 结构
{
tag: 'DIV',
attrs: {
id: "app"
},
children: [
{
tag: 'SPAN',
children: [
{
tag: 'A',
children: []
}
]
}
]
}
// 真实DOM 结构
<div id="app">
<span>
<a></a>
</span>
</div>
复制代码
实现:
function _render(vnode) {
// 如果是数字类型转化为字符串;
if(typeof vnode === "number") {
vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if(typeof vnode === "string") {
return document.createTextNode(vnode);
}
// 普通 DOM
const dom = document.createElement(vnode.tag);
if(vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach((key) => {
dom.setAttribute(key, vnode.attrs[key]);
});
}
// 子数组进行递归操作
vnode.children.forEach((child) => dom.appendChild(_render(child)));
return dom;
}
// 测试
let vnode = {
tag: "DIV",
attrs: {
id: "app",
},
children: [
{
tag: "SPAN",
children: [
{
tag: "A",
children: [],
},
],
},
],
};
console.log(_render(vnode)); // <div id="app"><span><a></a></span></div>
复制代码
评论