关于javascript:社招前端必会面试题附答案

1次阅读

共计 8916 个字符,预计需要花费 23 分钟才能阅读完成。

箭头函数的 this 指向哪⾥?

箭头函数不同于传统 JavaScript 中的函数,箭头函数并没有属于⾃⼰的 this,它所谓的 this 是捕捉其所在高低⽂的 this 值,作为⾃⼰的 this 值,并且因为没有属于⾃⼰的 this,所以是不会被 new 调⽤的,这个所谓的 this 也不会被扭转。

能够⽤ Babel 了解⼀下箭头函数:

// ES6 
const obj = {getArrow() {return () => {console.log(this === obj); 
    }; 
  } 
}

转化后:

// ES5,由 Babel 转译
var obj = {getArrow: function getArrow() { 
     var _this = this; 
     return function () {console.log(_this === obj); 
     }; 
   } 
};

数组去重

ES5 实现:

function unique(arr) {var res = arr.filter(function(item, index, array) {return array.indexOf(item) === index
    })
    return res
}

ES6 实现:

var unique = arr => [...new Set(arr)]

事件流传机制(事件流)

冒泡和捕捉

IE 兼容

  • attchEvent(‘on’ + type, handler)
  • detachEvent(‘on’ + type, handler)

代码输入后果

async function async1() {console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {console.log("async2");
}
async1();
console.log('start')

输入后果如下:

async1 start
async2
start
async1 end

代码的执行过程如下:

  1. 首先执行函数中的同步代码 async1 start,之后遇到了await,它会阻塞async1 前面代码的执行,因而会先去执行 async2 中的同步代码async2,而后跳出async1
  2. 跳出 async1 函数后,执行同步代码start
  3. 在一轮宏工作全副执行完之后,再来执行 await 前面的内容async1 end

这里能够了解为 await 前面的语句相当于放到了 new Promise 中,下一行及之后的语句相当于放在 Promise.then 中。

代码输入后果

async function async1 () {console.log('async1 start');
  await new Promise(resolve => {console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

输入后果如下:

script start
async1 start
promise1
script end

这里须要留神的是在 async1await前面的 Promise 是没有返回值的,也就是它的状态始终是 pending 状态,所以在 await 之后的内容是不会执行的,包含 async1 前面的 .then

代码输入后果

function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result:", res))
  .catch(err => console.log(err));

输入后果如下:

0
Error: 0
1
2
3

能够看到在 catch 捕捉到第一个谬误之后,前面的代码还不执行,不过不会再被捕捉了。

留神:allrace 传入的数组中如果有会抛出异样的异步工作,那么只有最先抛出的谬误会被捕捉,并且是被 then 的第二个参数或者前面的 catch 捕捉;但并不会影响数组中其它的异步工作的执行。

事件流

事件流是网页元素接管事件的程序,”DOM2 级事件 ” 规定的事件流包含三个阶段:事件捕捉阶段、处于指标阶段、事件冒泡阶段。
首先产生的事件捕捉,为截获事件提供机会。而后是理论的指标承受事件。最初一个阶段是工夫冒泡阶段,能够在这个阶段对事件做出响应。
尽管捕捉阶段在标准中规定不容许响应事件,然而实际上还是会执行,所以有两次机会获取到指标对象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> 事件冒泡 </title>
</head>
<body>
    <div>
        <p id="parEle"> 我是父元素    <span id="sonEle"> 我是子元素 </span></p>
    </div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');parEle.addEventListener('click', function () {alert('父级 冒泡');}, false);parEle.addEventListener('click', function () {alert('父级 捕捉');}, true);sonEle.addEventListener('click', function () {alert('子级冒泡');}, false);sonEle.addEventListener('click', function () {alert('子级捕捉');}, true);

</script>

当容器元素及嵌套元素,即在 捕捉阶段 又在 冒泡阶段 调用事件处理程序时:事件按 DOM 事件流的程序 执行事件处理程序:

  • 父级捕捉
  • 子级捕捉
  • 子级冒泡
  • 父级冒泡

且当事件处于指标阶段时,事件调用程序决定于绑定事件的 书写程序,按下面的例子为,先调用冒泡阶段的事件处理程序,再调用捕捉阶段的事件处理程序。顺次 alert 出“子集冒泡”,“子集捕捉”。

代码输入后果

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。

参考:前端进阶面试题具体解答

<script src=’xxx’’xxx’/> 内部 js 文件先加载还是 onload 先执行,为什么?

onload 是所以加载实现之后执行的

如何判断数组类型

Array.isArray

代码输入问题

function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = {demo: 5};
    this.show = function () {console.log(this.a , this.b , this.c.demo);
    }
}

function Child() {
    this.a = 2;
    this.change = function () {this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}

Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();

输入后果:

parent.show(); // 1  [1,2,1] 5

child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5

parent.show(); // 1 [1,2,1] 5

child1.show(); // 5 [1,2,1,11,12] 5

child2.show(); // 6 [1,2,1,11,12] 5

这道题目值得神帝,他波及到的知识点很多,例如 this 的指向、原型、原型链、类的继承、数据类型 等。

解析:

  1. parent.show(),能够间接取得所需的值,没啥好说的;
  2. child1.show(),Child的构造函数本来是指向 Child 的,题目显式将 Child 类的原型对象指向了 Parent 类的一个实例,须要留神 Child.prototype 指向的是 Parent 的实例 parent,而不是指向Parent 这个类。
  3. child2.show(),这个也没啥好说的;
  4. parent.show(),parent是一个 Parent 类的实例,Child.prorotype指向的是 Parent 类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响 parent 实例,所以输入后果不变;
  5. child1.show(),child1执行了 change() 办法后,产生了怎么的变动呢?
  6. this.b.push(this.a),因为 this 的动静指向个性,this.b 会指向 Child.prototype 上的 b 数组,this.a 会指向 child1a属性, 所以 Child.prototype.b 变成了[1,2,1,11];
  7. this.a = this.b.length,这条语句中 this.athis.b的指向与上一句统一,故后果为 child1.a 变为4;
  8. this.c.demo = this.a++,因为 child1 本身属性并没有 c 这个属性,所以此处的 this.c 会指向 Child.prototype.cthis.a 值为 4,为原始类型,故赋值操作时会间接赋值,Child.prototype.c.demo 的后果为 4,而this.a 随后自增为5(4 + 1 = 5)。
  9. child2执行了 change() 办法, 而 child2child1均是 Child 类的实例,所以他们的原型链指向同一个原型对象 Child.prototype, 也就是同一个parent 实例,所以 child2.change() 中所有影响到原型对象的语句都会影响 child1 的最终输入后果。
  10. this.b.push(this.a),因为 this 的动静指向个性,this.b 会指向 Child.prototype 上的 b 数组,this.a 会指向 child2a属性, 所以 Child.prototype.b 变成了[1,2,1,11,12];
  11. this.a = this.b.length,这条语句中 this.athis.b的指向与上一句统一,故后果为 child2.a 变为5;
  12. this.c.demo = this.a++,因为 child2 本身属性并没有 c 这个属性,所以此处的 this.c 会指向 Child.prototype.c,故执行后果为Child.prototype.c.demo 的值变为 child2.a 的值 5,而child2.a 最终自增为6(5 + 1 = 6)。

代码输入后果

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 捕捉到。

说一说你用过的 css 布局

gird 布局,layout 布局,flex 布局,双飞翼,圣杯布局等

new 一个构造函数,如果函数返回 return {}return nullreturn 1return true 会产生什么状况?

如果函数返回一个对象,那么 new 这个函数调用返回这个函数的返回对象,否则返回 new 创立的新对象

Promise.any

形容 :只有 promises 中有一个fulfilled,就返回第一个fulfilledPromise实例的返回值。

实现

Promise.any = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return reject(new AggregateError("All promises were rejected"));
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(value => resolve(value),
                    reason => {
                        count++;
                        if(count === promises.length) {reject(new AggregateError("All promises were rejected"));
                        };
                    }
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

代码输入后果

function a() {console.log(this);
}
a.call(null);

打印后果:window 对象

依据 ECMAScript262 标准规定:如果第一个参数传入的对象调用者是 null 或者 undefined,call 办法将把全局对象(浏览器上是 window 对象)作为 this 的值。所以,不论传入 null 还是 undefined,其 this 都是全局对象 window。所以,在浏览器上答案是输入 window 对象。

要留神的是,在严格模式中,null 就是 null,undefined 就是 undefined:

'use strict';

function a() {console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined

图片懒加载

实现getBoundClientRect 的实现形式,监听 scroll 事件(倡议给监听事件增加节流),图片加载完会从 img 标签组成的 DOM 列表中删除,最初所有的图片加载结束后须要解绑监听事件。

// scr 加载默认图片,data-src 保留施行懒加载后的图片
// <img src="./default.jpg" data-src="https://xxx.jpg" alt="" />
let imgs = [...document.querySelectorAll("img")];
const len = imgs.length;

let lazyLoad = function() {
    let count = 0;
    let deleteImgs = [];
    // 获取以后可视区的高度
    let viewHeight = document.documentElement.clientHeight;
    // 获取以后滚动条的地位(间隔顶部的间隔, 等价于 document.documentElement.scrollTop)
    let scrollTop = window.pageYOffset;
    imgs.forEach((img) => {
        // 获取元素的大小,及其绝对于视口的地位,如 bottom 为元素底部到网页顶部的间隔
        let bound = img.getBoundingClientRect();
        // 以后图片间隔网页顶部的间隔
        // let imgOffsetTop = img.offsetTop;

        // 判断图片是否在可视区内,如果在就加载(两种判断形式)// if(imgOffsetTop < scrollTop + viewHeight) 
        if (bound.top < viewHeight) {
            img.src = img.dataset.src;  // 替换待加载的图片 src
            count++;
            deleteImgs.push(img);
            // 最初所有的图片加载结束后须要解绑监听事件
            if(count === len) {document.removeEventListener("scroll", imgThrottle);
            }
        }
    });
    // 图片加载完会从 `img` 标签组成的 DOM 列表中删除
    imgs = imgs.filter((img) => !deleteImgs.includes(img));
}

window.onload = function () {lazyLoad();
};
// 应用 防抖 / 节流 优化一下滚动事件
let imgThrottle = debounce(lazyLoad, 1000);
// 监听 `scroll` 事件
window.addEventListener("scroll", imgThrottle);

代码输入后果

function fn1(){console.log('fn1')
}
var fn2

fn1()
fn2()

fn2 = function() {console.log('fn2')
}

fn2()

输入后果:

fn1
Uncaught TypeError: fn2 is not a function
fn2

这里也是在考查变量晋升,关键在于第一个 fn2(),这时 fn2 仍是一个 undefined 的变量,所以会报错 fn2 不是一个函数。

虚构 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>
正文完
 0