箭头函数的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 startasync2startasync1 end
代码的执行过程如下:
- 首先执行函数中的同步代码
async1 start
,之后遇到了await
,它会阻塞async1
前面代码的执行,因而会先去执行async2
中的同步代码async2
,而后跳出async1
; - 跳出
async1
函数后,执行同步代码start
; - 在一轮宏工作全副执行完之后,再来执行
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 startasync1 startpromise1script end
这里须要留神的是在async1
中await
前面的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));
输入后果如下:
0Error: 0123
能够看到在catch捕捉到第一个谬误之后,前面的代码还不执行,不过不会再被捕捉了。
留神:all
和race
传入的数组中如果有会抛出异样的异步工作,那么只有最先抛出的谬误会被捕捉,并且是被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)
输入后果如下:
13842567
代码执行过程如下:
- 首先执行script代码,打印出1;
- 遇到第一个定时器,退出到宏工作队列;
- 遇到Promise,执行代码,打印出3,遇到resolve,将其退出到微工作队列;
- 遇到第二个定时器,退出到宏工作队列;
- 遇到第三个定时器,退出到宏工作队列;
- 继续执行script代码,打印出8,第一轮执行完结;
- 执行微工作队列,打印出第一个Promise的resolve后果:4;
- 开始执行宏工作队列,执行第一个定时器,打印出2;
- 此时没有微工作,继续执行宏工作中的第二个定时器,首先打印出5,遇到Promise,首选打印出6,遇到resolve,将其退出到微工作队列;
- 执行微工作队列,打印出6;
- 执行宏工作队列中的最初一个定时器,打印出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] 5child1.show(); // 11 [1,2,1] 5child2.show(); // 12 [1,2,1] 5parent.show(); // 1 [1,2,1] 5child1.show(); // 5 [1,2,1,11,12] 5child2.show(); // 6 [1,2,1,11,12] 5
这道题目值得神帝,他波及到的知识点很多,例如this的指向、原型、原型链、类的继承、数据类型等。
解析:
- parent.show(),能够间接取得所需的值,没啥好说的;
- child1.show(),
Child
的构造函数本来是指向Child
的,题目显式将Child
类的原型对象指向了Parent
类的一个实例,须要留神Child.prototype
指向的是Parent
的实例parent
,而不是指向Parent
这个类。 - child2.show(),这个也没啥好说的;
- parent.show(),
parent
是一个Parent
类的实例,Child.prorotype
指向的是Parent
类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响parent
实例,所以输入后果不变; - child1.show(),
child1
执行了change()
办法后,产生了怎么的变动呢? - this.b.push(this.a),因为this的动静指向个性,this.b会指向
Child.prototype
上的b数组,this.a会指向child1
的a属性,所以Child.prototype.b
变成了[1,2,1,11]; - this.a = this.b.length,这条语句中
this.a
和this.b
的指向与上一句统一,故后果为child1.a
变为4; - this.c.demo = this.a++,因为
child1
本身属性并没有c这个属性,所以此处的this.c
会指向Child.prototype.c
,this.a
值为4,为原始类型,故赋值操作时会间接赋值,Child.prototype.c.demo
的后果为4,而this.a
随后自增为5(4 + 1 = 5)。 child2
执行了change()
办法, 而child2
和child1
均是Child
类的实例,所以他们的原型链指向同一个原型对象Child.prototype
,也就是同一个parent
实例,所以child2.change()
中所有影响到原型对象的语句都会影响child1
的最终输入后果。- this.b.push(this.a),因为this的动静指向个性,this.b会指向
Child.prototype
上的b数组,this.a会指向child2
的a属性,所以Child.prototype.b
变成了[1,2,1,11,12]; - this.a = this.b.length,这条语句中
this.a
和this.b
的指向与上一句统一,故后果为child2.a
变为5; - 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 null
, return 1
, return true
会产生什么状况?
如果函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,否则返回 new 创立的新对象
Promise.any
形容:只有 promises
中有一个fulfilled
,就返回第一个fulfilled
的Promise
实例的返回值。
实现
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); // nulla.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 fn2fn1()fn2()fn2 = function() { console.log('fn2')}fn2()
输入后果:
fn1Uncaught TypeError: fn2 is not a functionfn2
这里也是在考查变量晋升,关键在于第一个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>