前端面试资料整理【javascript篇】

共 11474字,需浏览 23分钟

 ·

2022-05-16 15:54

作者:donggg

来源:SegmentFault  思否社区 


块级作用域(执行结果题)



块级作用域:https://blog.poetries.top/browser-working-principle/guide/part2/lesson09.html


阮一峰 块级作用域:https://www.bookstack.cn/read/es6-3rd/spilt.2.docs-let.md


var a = 1999;
{
    console.log(a); // function a(){}
    a = 2020;
    function a() {}
    a = 2021;
}
console.log(a); // 2020


我的理解:(原理可能错误,但是便于理解,错误点在于块级不能声明函数,浏览器会有自己的支持行为,类似于函数表达式声明——参考阮一峰。声明也不要用函数声明的方式,用函数表达式的方式。)


在块级中,编译过程,函数声明变量提升,执行过程无影响。因此是 function a(){}在块级外部,编译过程,外部有a,无影响。执行过程时,开始查找,由于 a 的查找顺序是从词法环境({})到变量环境(var),查找到最近的,因此是 2020(注意此处是执行阶段,function a(){} 的变量提升是编译阶段)


ps.函数和变量相比,会被优先提升。(我的理解:这意味着函数会替换掉变量提升)


事件冒泡、事件捕获、事件代理(事件委托)



事件冒泡:略


事件捕获:略


事件代理:利用事件冒泡,将事件绑定在父元素中

target.addEventListener(type, listener, useCapture); ,其中useCapture 为 false 时,为事件冒泡,默认是 false


Object



常见的方法:


  • Object.defineProperty 定义的 description: { value, writable, configurable, emunable },或者 { set, get, configurable, emunable }

  • Object.create 、Object.keys、Object.values、Object.entries、Object.toString

  • Object.preventExtensions 组织扩展对象


作用域、作用域链和上下文



作用域是指在函数定义时,声明变量的空间。


作用域链是指在变量查找过程中,从当前上下文查找,逐层往父级,直至全局。
函数声明时,会有个 scope 属性,包含所有父级的变量。此时 VO对象,包含内部函数、变量、形参,存储在上下文中。

函数执行时,AO对象,包含内部函数、变量、形参、内部this,挂载到作用域链上,

作用域、作用域链与执行上下文栈入门了解:https://blog.csdn.net/star66666651/article/details/107168254


原形链



待补充


Promise 与异步



常见的异步请求:


var request = new HttpXMLRequest()
request.open('GET', url);
request.responseType = 'json'
request.onload = function(){}
request.send()


原生

实现Promise

待补充

// Promise

// Promise.prototype.then

// Promise.prototype.all

// Promise.prototype.resolve

// Promise.prototype.race

事件循环与事件队列



事件循环

组成:

  • 事件队列(单位是消息,消息关联着回调函数,从消息队列中弹出后会调用回调函数,形成执行帧)

  • 执行栈(单位是帧,包含函数中的变量与参数)

  • 堆(保存对象结构)


同步任务与异步任务
window.requestAnimationFrame() 既不是宏任务也不是微任务,而是在浏览器下次重绘的时候执行


闭包



两个主要的特点:

  • 通过函数阻止外部函数对内部变量的引用
  • 函数可以使用外部的变量

参考:http://jartto.wang/2017/12/18/reflective-closure/

闭包中的this

由于闭包是执行在内存中,所以 this 通常指向全局,可以通过 call 改变。

闭包的作用

  • 通过立即执行函数,模拟块级作用域,减少向全局作用域声明变量,另外由于立即执行函数在执行完后外部没有引用,那么内存会立即释放

  • 使用 var 声明时,利用立即执行函数遍历时 i 能准确获取

  • 实现面向对象编程(不是通过 new 构造)


function Person(){
  var name = 'default';
  return {
    getName : function(){
      return name;
    },
    setName : function(newName){
      name = newName;
    }
  }
};
var p1 = Person();
console.log(p1.getName()); // default
p1.setName('wang');
console.log(p1.getName()); // wang

闭包的问题

  • 在闭包中引用 dom 会导致循环引用,无法 GC(引用计数)

// IE9 之前甚至无法销毁 dom
function closure(){
  var element = document.getElementById(ID);
  element.onclick = function(){
    console.log(element.id);
  }
  // 销毁 element
  // element = null
}

  • this 指向
  • 闭包返回局部作用域变量时,内存不会立即释放


宏任务 和 微任务



常见异步考题:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7


es5/es6/es7/es8



待补充


class 中的 super



super 既可以当函数也可以当对象使用。


当函数时,相当于是父类的构造函数,只能用在子类的构造函数中,this 指向子类实例。

class A {
  constructor() {
    this.show();
  }
  show(){
    console.log('A 实例');
  }
}
class B extends A {
  constructor() {
    super();
  }
  show(){
    console.log('B 实例');
  }
}
new B() // B 实例

当对象时,在一般函数使用时,super 相当于父类原型对象,this 指向子类实例 。

class A {
  constructor() {  
    this.x = 'A';
  }
  say() {
    console.log(this.x)
  }
}
class B extends A {
  constructor() {
    super();
    this.x = 'B'
  }
}
let b = new B();
console.log(b.say()) // B

ps. 注意该提醒

class A {
  constructor() {  // 在构造函数上定义的属性和方法相当于定义在父类实例上的,而不是原型对象上
    this.p = 2;
  }
}
class B extends A {
  get m() {
    return super.p;
  }
}
let b = new B();
console.log(b.m) // undefined

// 引申题
function A(x) {
    this.p = x
}
A.prototype.p = 2
// 此时的 p 通过构造函数已经声明
new A().p // undefined

super 在静态方法 this 指向父类


常见的代码



防抖与节流


防抖是指,在一段时间内累计后触发,例如输入框输入文字时,监听 onChange。

function debounce(fn, delay) {
    let timer
    return function () {
        const self = this;
        const args = arguments;
        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(function(){
            fn.apply(self, args);
        }, delay)
    }
}

let log = debounce(function(){ console.log('!')}, 5000)

window.addEventListener('resize',log)

节流是指,在一段时间内多次重复触发仅执行一次,例如重复点击。

function throttle(fn, delay) {

    let timer
    
    return function () {
        const self = this;
        const args = arguments;
        if (timer) {
            return;
        }

        timer = setTimeout(function() {
            self.apply(fn, args)
            timer = null;
        }, delay)
    }
}

let log = throttle(function(){ console.log('!')}, 3000)

window.addEventListener('click',log)

形成这种区别的原因:

节流当第一次执行是 arg 就固定了,也就是说如果用节流放到输入框 onChange 场景时,值将是第一个输入的数字。

防抖,通过不断的 clearTimeout,来更新要执行的函数,直到不触发后,等待 delay 后执行,delay 的作用是在此期间如果再次触发,则会再次 clearTimeout

手写new



// Object.create 会更新 __proto__,也就是 [[Prototype]],维持原形链
function create (proto) {
    if (typeof proto !== 'object' && typeof proto !== 'function' ) {
        throw new TypeError("原型只能是对象")
    }
    if (proto === null) {
        throw new TypeError("不能为空")
    }

        // function F() {}
        //F.prototype = proto;
        // return new F();
    proto.constructor.prototype = proto
    return new proto.constructor()
}

function newOperator(ctor) {
    if (typeof ctor !== 'function') {
        throw '构造函数必须是方法'
    }

    newOperator.target = ctor;

    // es6 可以直接使用 Object.create
    // const instance = Object.create(ctor.prototype)
    const instance = create(ctor.prototype)
    const args = [].slice.call(arguments, 1)
    // 绑定 this,并执行构造函数
    const r = ctor.apply(instance, args);
    // 如果构造函数有返回,则返回构造函数
    if (r) {
        return r;
    }

    // 实例
    return instance;
}

function Person (name) {
    this.name = name
}


const w = newOperator(Person, "zs")

console.log(w)


手写bind



function bind(fn, obj) {
    const args = [].slice.call(arguments, 1);
    const selef = this;
    return function bound() {
        return fn.call(obj, [].slice.call(arguments, 1).concat(args))
    }
}

const h = {
    name: 'zs',
}

function say() {
    console.log(this.name)
}

const bound = bind(say, h)
bound()


Object.is Polyfill



if (!Object.is) {
  Object.defineProperty(Object, "is", {
    value: function (x, y) {
        if (x === y) {
            // 1. 如果 x === y,且均不为0时
            // 2. 如果 x,y 均为 0 ,判断符号是否相等
            return x !== 0 || 1 / x === 1 / y; 
        } else {
            // NaN 与自己比较。包含:Number.NaN, 0/0, NaN
            return x != x && y != y;
        }
    }
  })
}


如何实现 Array.reduce()



待补充


curry 与 compose



待补充


Object.assign()



待补充


实现字符串 repeat



// 原生repeat 'ni'.repeat(3); 
// 'ninini' 
// 实现一 
String.prototype.repeatString1 = function (n) { 
    return Array(n + 1).join(this); 

console.log('ni'.repeatString1(3)); 
// 实现二 
String.prototype.repeatString2 = function (n) { 
    return Array(n).fill(this).join('');
}
console.log('ni'.repeatString2(3));


js 模板引擎



Function('let a = 1');


其他



https://juejin.cn/post/6844903575559077895#heading-16



点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报