乐趣区

关于javascript:2023前端二面常考面试题合集

说下对 JS 的理解吧

是基于原型的动静语言,次要独特个性有 this、原型和原型链。

JS 严格意义上来说分为:语言规范局部(ECMAScript)+ 宿主环境局部

语言规范局部

2015 年公布 ES6,引入诸多新个性使得可能编写大型项目变成可能,规范自 2015 之后以年号代号,每年一更

宿主环境局部

  • 在浏览器宿主环境包含 DOM + BOM 等
  • 在 Node,宿主环境包含一些文件、数据库、网络、与操作系统的交互等

说一下 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

解析:

  1. obj.foo(),foo 的 this 指向 obj 对象,所以 a 会输入 2;
  2. obj.bar(),printA 在 bar 办法中执行,所以此时 printA 的 this 指向的是 window,所以会输入 1;
  3. 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)

输入后果如下:

1

看到这个题目,好多的 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

解析:

  1. obt.fn(),fn 是由 obt 调用的,所以其 this 指向 obt 对象,会打印出 20;
  2. obt.fn.call(),这里 call 的参数啥都没写,就示意 null,咱们晓得如果 call 的参数为 undefined 或 null,那么 this 就会指向全局对象 this,所以会打印出 10;
  3. (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)
  })

输入后果如下:

error 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
7
6
8
2
4
3
5
9
11
10
12

(1)第一轮事件循环流程剖析如下:

  • 整体 script 作为第一个宏工作进入主线程,遇到console.log,输入 1。
  • 遇到setTimeout,其回调函数被散发到宏工作 Event Queue 中。暂且记为setTimeout1
  • 遇到process.nextTick(),其回调函数被散发到微工作 Event Queue 中。记为process1
  • 遇到 Promisenew Promise 间接执行,输入 7。then被散发到微工作 Event Queue 中。记为then1
  • 又遇到了setTimeout,其回调函数被散发到宏工作 Event Queue 中,记为setTimeout2
宏工作 Event Queue 微工作 Event Queue
setTimeout1 process1
setTimeout2 then1

上表是第一轮事件循环宏工作完结时各 Event Queue 的状况,此时曾经输入了 1 和 7。发现了 process1then1两个微工作:

  • 执行process1,输入 6。
  • 执行then1,输入 8。

第一轮事件循环正式完结,这一轮的后果是输入 1,7,6,8。

(2)第二轮工夫循环从 **setTimeout1** 宏工作开始:

  • 首先输入 2。接下来遇到了process.nextTick(),同样将其散发到微工作 Event Queue 中,记为process2
  • new Promise立刻执行输入 4,then也散发到微工作 Event Queue 中,记为then2
宏工作 Event Queue 微工作 Event Queue
setTimeout2 process2
then2

第二轮事件循环宏工作完结,发现有 process2then2两个微工作能够执行:

  • 输入 3。
  • 输入 5。

第二轮事件循环完结,第二轮输入 2,4,3,5。

(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。

  • 间接输入 9。
  • process.nextTick() 散发到微工作 Event Queue 中。记为process3
  • 间接执行new Promise,输入 11。
  • then 散发到微工作 Event Queue 中,记为then3
宏工作 Event Queue 微工作 Event Queue
process3
then3

第三轮事件循环宏工作执行完结,执行两个微工作 process3then3

  • 输入 10。
  • 输入 12。

第三轮事件循环完结,第三轮输入 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)

输入后果如下:

1
3
8
4
2
5
6
7

代码执行过程如下:

  1. 首先执行 script 代码,打印出 1;
  2. 遇到第一个定时器,退出到宏工作队列;
  3. 遇到 Promise,执行代码,打印出 3,遇到 resolve,将其退出到微工作队列;
  4. 遇到第二个定时器,退出到宏工作队列;
  5. 遇到第三个定时器,退出到宏工作队列;
  6. 继续执行 script 代码,打印出 8,第一轮执行完结;
  7. 执行微工作队列,打印出第一个 Promise 的 resolve 后果:4;
  8. 开始执行宏工作队列,执行第一个定时器,打印出 2;
  9. 此时没有微工作,继续执行宏工作中的第二个定时器,首先打印出 5,遇到 Promise,首选打印出 6,遇到 resolve,将其退出到微工作队列;
  10. 执行微工作队列,打印出 6;
  11. 执行宏工作队列中的最初一个定时器,打印出 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

解析:

  1. 首先执行 obj1.foo(2); 会在 obj 中增加 a 属性,其值为 2。之后执行 obj1.a,a 是右 obj1 调用的,所以 this 指向 obj,打印出 2;
  2. 执行 obj1.foo.call(obj2, 3) 时,会将 foo 的 this 指向 obj2,前面就和下面一样了,所以会打印出 3;
  3. obj1.a 会打印出 2;
  4. 最初就是考查 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

解析:

  1. 首先 func 是由 myObject 调用的,this 指向 myObject。又因为 var self = this; 所以 self 指向 myObject。
  2. 这个立刻执行匿名函数表达式是由 window 调用的,this 指向 window。立刻执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。

说一说什么是跨域,怎么解决

因为浏览器出于平安思考,有同源策略。也就是说,如果协定、域名或者端口有一个不同就是跨域,Ajax 申请会失败。为来避免 CSRF 攻打
1.JSONP
    JSONP 的原理很简略,就是利用 <script> 标签没有跨域限度的破绽。通过 <script> 标签指向一个须要拜访的地址并提供一个回调函数来接收数据当须要通信时。<script src="http://domain/api?param1=a&param2=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

解析:

  1. 最要害的就是 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。
  2. 当指向 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>
退出移动版