关于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>

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理