最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端常见面试题总结(一)

    正文概述 掘金(Orime小猪)   2021-01-27   369

    前端常见面试题总结(一)

    五月份面试时候总结的前端面试题,个别题目加上了个人理解,不保证正确性嗷,欢迎指正~


    什么是原型?

    • 原型分为两种:构造函数原型,称为prototype;实例(对象)原型称为__proto__

    原型之间的关系?

    • 由构造函数创建出来的实例对象上,__proto__属性指向了构造函数的prototype属性

    构造函数和构造器是什么?

    • js中,默认每一个(正常定义的)函数,都是构造器,也称为类,那么构造函数上的原型链上有ƒ Function() { [native code] }这个属性,所以可以通过new 方法创建实例,构造函数原型prototype上有一个constructor构造器属性,指向了构造函数自身
    • 能够称为构造器的关键在于拥有--->prototype属性(实例没有)
    function Fa() {}
    
    Fa.constructor
    ƒ Function() { [native code] }
    
    • 被new出来的实例,由于不是一个构造器,所以无法通过new创建新的实例--->没有prototype属性
    const son = new Fa()
    
    son.constructor
    // ƒ Fa() {}
    const subSon = new son()
    // VM5278:1 Uncaught TypeError: son is not a constructor
        at <anonymous>:1:16
    
    • 一张图描述清楚构造器想关
    • 前端常见面试题总结(一)

    __proto__ 存在于所有的对象上,prototype 存在于所有的函数上,他俩的关系就是:函数的 prototype 是所有使用 new 这个函数构造的实例的 __proto__。函数也是对象,所以函数同时有 __proto__prototype

    原型链是什么?

    • 实例拥有的实例原型对象__proto__属性,指向了构造函数原型对象prototype属性,而构造函数原型对象prototype上的__proto__属性也指向了父构造函数的原型prototype,以此类推,通过__proto__建立起来的指向关系就被称为原型链

    原型链的作用?

    • 实例访问一个属性的时候,先在自身可遍历属性上寻找,再到原型链上逐层向上寻找,如果找不到会返回undefined
    • stu.proto.proto.proto,直到遇到null,则返回undefined
    • 前端常见面试题总结(一)
    • 作用:将类的方法定义在原型上,通过这种方式,所有的实例都可以访问到这个方法,并且这个方法只需要占用一份内存,节省内存,this 的指向还能正确指向类的实例。
    • 注意:这种方法定义的属性,由于不是自身属性,所以无法通过Object.keys()枚举
    function Engineer(workingYears) {
      this.workingYears = workingYears;
    }
    
    // 不能使用箭头函数,箭头函数的 this 在声明的时候就根据上下文确定了
    Engineer.prototype.built = function() {
      // this 这里就是执行函数调用者
      console.log(`我已经工作 ${this.workingYears} 年了, 我的工作是拧螺丝...`);
    };
    
    const engineer = new Engineer(5);
    // this 会正确指向实例,所以 this.workingYears 是 5
    engineer.built(); // => 我已经工作 5 年了, 我的工作是拧螺丝...
    console.log(Object.keys(engineer)); // => [ 'workingYears' ]
    
    
    • 优点:通过这种方式,所有的实例都可以访问到这个方法,并且这个方法只需要占用一份内存,节省内存,this 的指向还能正确指向类的实例。
    • 不可枚举示例:
    function Engineer(workingYears) {
      this.workingYears = workingYears;
    }
    
    // 不能使用箭头函数,箭头函数的 this 在声明的时候就根据上下文确定了
    Engineer.prototype.built = function() {
      // this 这里就是执行函数调用者
      console.log(`我已经工作 ${this.workingYears} 年了, 我的工作是拧螺丝...`);
    };
    
    const engineer = new Engineer(5);
    // this 会正确指向实例,所以 this.workingYears 是 5
    engineer.built(); // => 我已经工作 5 年了, 我的工作是拧螺丝...
    console.log(Object.keys(engineer)); // => [ 'workingYears' ]
    
    
    • 可枚举方法示例:
    function Engineer(workingYears) {
      this.workingYears = workingYears;
      this.built = function() {
        console.log(`我已经工作 ${this.workingYears} 年了, 我的工作是拧螺丝...`);
      };
    }
    
    const engineer = new Engineer(5);
    console.log(Object.keys(engineer)); // => [ 'workingYears', 'built' ]
    
    • 常见:Array.prototype.slice,Object.prototype.toString就是定义在了原型上的函数。

    # ES6的class本质是什么?
    • 其实,ES6 class 就是构造器的语法糖
    • ES6 的 class 就是构造器,class 上的方法定义在构造器的 prototype 上,因此你也可以理解为什么 class 的方法是不可枚举的。

    # class extends实现继承的本质?
    • 原型继承+组合继承
    // 原型继承
    function _inheritsLoose(subClass, superClass) {
      subClass.prototype = Object.create(superClass.prototype);
      subClass.prototype.constructor = subClass;
      // 让子类可以访问父类上的静态属性,其实就是定义在构造器自身上的属性
      // 例如父类有 Person.say 属性,子类 Student 通过可以通过 Student.say 访问
      subClass.__proto__ = superClass;
    }
    
    var Shape = function Shape(x, y) {
      this.x = x;
      this.y = y;
    };
    
    var Circle = (function(_Shape) {
      _inheritsLoose(Circle, _Shape);
    
      function Circle(x, y, r) {
        var _this;
    
        // 组合继承
        _this = _Shape.call(this, x, y) || this;
        _this.r = r;
        return _this;
      }
    
      var _proto = Circle.prototype;
    
      _proto.draw = function draw() {
        console.log(
          '\u753B\u4E2A\u5750\u6807\u4E3A (' +
            this.x +
            ', ' +
            this.y +
            ')\uFF0C\u534A\u5F84\u4E3A ' +
            this.r +
            ' \u7684\u5706'
        );
      };
    
      return Circle;
    })(Shape);
    
    

    继承有哪几种方式?

    (1)借助构造函数+call/apply实现继承

    // 借助构造函数实现继承
    function Parent(argument) {
    	this.name='parent';
    }
    Parent.prototype.say=function(){}
    function Child(argument) {
    	Parent.call(this); // 原理就是call/apply, call和apply改变了父类this中的指向,使this指向了子类,这样就可以把父类的属性挂载到子类里
    	this.age=11;
    }
    var child = new Child();
    console.log(child);  // Child {name: "parent", age: 11}
    

    缺点是只能继承父类实例上的属性,无法继承原型链上的属性。
    (2)借助原型链实现继承

    // 借助原型链实现继承
    function Parent1(argument) {
    	this.name='parent';
    	this.age=[1,2,3];
    }
    function Child1(argument) {
    	this.name='child';
    }
    Child1.prototype=new Parent1(); // 将父类的实例赋值給子类的原型,这样子类就继承了父类
    var child11 = new Child1();
    console.log(child11)  // Child1 {age: 11 , __proto__: Parent1}
    /*******************原型链继承的缺点********************/
    var child12=new Child1();
    child11.age.push(4); // 往其中一个实例的引用属性添加一个元素
    console.log(child11.age,child12.age) // 会发现都是打印出  [1, 2, 3, 4]
    

    缺点:当父类有引用属性时,由于原型对象的特点,多个实例对象的__proto__都是同一个,而引用属性在new的时候不会开辟新的地址,所以当一个实例对象改变了引用属性的值时,另一个对象也会随之改变。
    (3)结合构造函数和原型链的方式

    // 组合方式,结合上面两种
    function Parent3(argument) {
    	this.name='parent';
    	this.age=[1,2,3]
    }
    Parent3.prototype.say=function(){}
    function Child3(argument) {
    	Parent3.call(this); // 结合构造函数
    	this.type='test';
    }
    Child3.prototype=new Parent3();  // 结合原型链
    var child31 = new Child3();
    var child32 = new Child3();
    console.log(child31,child32);
    child31.age.push(4);
    console.log(child31.age,child32.age);.//  [1, 2, 3, 4]      [1, 2, 3]
    

    这种方式可以解决前两种方式的问题。缺点:父类的构造函数会执行两次。
    优化:把上面的

    Child3.prototype=new Parent3();
    

    换成

    Child3.prototype=Parent3.prototype;
    

    以上方法还是存在不足,因为只要是通过原型链继承来的对象,它的constructor打印出来都是父类Parent3,即无法确认child31实例是由父类创造的还是由子类创造的。 原因:Child3和父类Parent3共用了一个原型。Child本身没有constructor,由于继承了父类,就会把父类的constructor作为自己的。
    解决方案:把上面的

    Child3.prototype=Parent3.prototype;
    

    换成

    Child3.prototype=Object.create(Parent3.prototype); // 让Child3继承Parent3,由于Object.create会返回一个新对象,该对象继承了Parent3,再让Child3去继承Parent3,这样就起到了隔离并且继承的作用。
    Child3.prototype.constructor=Child3;
    // 修改Child3的constructor
    

    这样的话就是组合继承+原型继承的完美写法了。

    // 组合方式,完美写法
    function Parent3(argument) {
    	this.name='parent';
    	this.age=[1,2,3];
    }
    Parent3.prototype.say=function(){}
    function Child3(argument) {
    	Parent3.call(this); // 结合构造函数
    	this.type='test';
    }
    Child3.prototype=Object.create(Parent3.prototype);
    Child3.prototype.constructor=Child3; // 结合原型链
    var child31 = new Child3();
    var child32 = new Child3();
    console.log(child31,child32);
    child31.age.push(4);
    console.log(child31.age,child32.age);.//  [1, 2, 3, 4]      [1, 2, 3]
    

    Object.create是做什么的?

    • Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
    const person = {
      isHuman: false,
      printIntroduction: function () {
        console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
      }
    };
    
    const me = Object.create(person);
    
    me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
    me.isHuman = true; // inherited properties can be overwritten
    
    me.printIntroduction();
    // expected output: "My name is Matthew. Am I human? true"
    me.__proto__===person;  // true
    
    

    一道最近校招面试碰到的和原型相关的面试题

    最近面试某大厂碰到下面这道面试题:

    function Page() {
      return this.hosts;
    }
    Page.hosts = ['h1'];
    Page.prototype.hosts = ['h2'];
    const p1 = new Page();
    const p2 = Page();
    console.log(p1.hosts);
    console.log(p2.hosts);
    复制代码
    

    运行结果是:先输出 undefiend,然后报错 TypeError: Cannot read property 'hosts' of undefined
    为什么 console.log(p1.hosts) 是输出 undefiend 呢,前面我们提过 new 的时候如果 return 了对象,会直接拿这个对象作为 new 的结果,因此,p1 应该是 this.hosts 的结果,而在 new Page() 的时候,this 是一个以 Page.prototype 为原型的 target 对象,所以这里 this.hosts 可以访问到 Page.prototype.hosts 也就是 ['h2']。这样 p1 就是等于 ['h2']['h2'] 没有 hosts 属性所以返回 undefined
    为什么 console.log(p2.hosts) 会报错呢,p2 是直接调用 Page 构造函数的结果,直接调用 page 函数,这个时候 this 指向全局对象,全局对象并没 hosts 属性,因此返回 undefined,往 undefined 上访问 hosts 当然报错。

    作者:余腾靖
    链接:juejin.im/post/5e2ff7…
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    proxy实现观察者模式

    
    const observerQueue = new Set()
    
    const observe = fn => observerQueue.add(fn)
    
    const observable = obj => new Proxy(obj, 
    	set(tgt, key, val, receiver) {
    		const result = Reflect.set(tgt, key, val, recevier)
    		observerQueue.forEach(v => v())
    		return result
    	}
    )
    
    const person = observerable({age: 25, name: 'Mike'})
    const print = () => console.log(`${person.name} is ${person.age} years old`)
    observe(print)
    
    person.name = 'LiHua'
    // Lihua is 25 years old
    person.age = 45
    // Lihua is 45 years old
    

    Common.js和ES6的module比较

    • CommonJS 输出是值的拷贝,即原来模块中的值改变不会影响已经加载的该值,ES6静态分析,动态引用,输出的是值的引用,值改变,引用也改变,即原来模块中的值改变则该加载的值也改变。
    • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
    • CommonJS 加载的是整个模块,即将所有的接口全部加载进来,ES6 可以单独加载其中的某个接口(方法),
    • CommonJS this 指向当前模块,ES6 this 指向undefined
    • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

    ————————————————
    版权声明:本文为CSDN博主「冰雪为融」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:blog.csdn.net/lhjuejiang/…

    yield传值与不传值的区别是什么?

    • yield每次调用next的时候传值,将作为上一次yield语句后的返回值
    • 如果不传值则值为undefined
    // 传值
    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next(12)) // => {value: 8, done: false}
    console.log(it.next(13)) // => {value: 42, done: true}
    
    // 不传值
    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next()) // => {value: 8, done: false}
    console.log(it.next()) // => {value: 42, done: true}
    VM5628:7 {value: 6, done: false}
    VM5628:8 {value: NaN, done: false}
    VM5628:9 {value: NaN, done: true}
    

    yield常见用途

    function *fetch() {
        yield ajax(url, () => {})
        yield ajax(url1, () => {})
        yield ajax(url2, () => {})
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()
    

    async函数的执行顺序

    • async中await修饰的语句会同步执行
    • async函数执行之后返回一个promise,里面执行的函数会作为回调函数在外部执行栈结束之后执行
    let c = 0
    let d = async () => {
        c = c + await 10
        console.log('函数内部1')
        await console.log('函数内部2', c)
        console.log('函数内部3')
    }
    undefined
    d()
    c++
    console.log('函数外部')
    VM2692:3 函数外部
    VM2618:4 函数内部1
    VM2618:5 函数内部2 10
    VM2618:6 函数内部3
    

    经典例题实现Promise.race和Promise.all

    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('p1准备执行')
        resolve('p1执行了')
      }, 1000)
    })
    
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('p2执行了')
      }, 2000)
    })
    
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('p3执行失败了')
      }, 3000)
    })
    
    Promise.race([p1, p2, p3]).then(res => console.log(res), err => console.log(err))
    
    p1准备执行
    p1执行了
    
    Promise.all([p1, p2, p3]).then(res => console.log(res), err => console.log(err))
    
    p1准备执行
    p3执行失败了
    
    
    

    手写简版的Promise

    •  参考地址:Promise/A+规范
    • 简版实现链接
    // <!--  简易版的promise -->
    const PENDING = "pending"
    const RESOLVE = "resolve"
    const REJECT = "reject"
    
    function MyPromise(fn) {
      const that = this
      that.status = PENDING // MyPromise 内部状态
      that.value = null // 传入 resolve 和 reject 的值
      that.resolveCallbacks = [] // 保存 then 中resolve的回调函数
      that.rejectCallbacks = [] // 保存 then 中reject的回调函数
    
      // resolve 函数 Promise内部调用 resolve 函数 例:new MyPromise((resolve,reject)=>{resolve(1)})
      function resolve(val) {
        if (that.status === PENDING) {
          that.status = RESOLVE
          that.value = val
          that.resolveCallbacks.forEach(cb => cb(that.value))
        }
      }
      // reject 函数 Promise内部调用的 reject 函数 例:new MyPromise((resolve,reject)=>{reject(1)})
      function reject(val) {
        if (that.status === PENDING) {
          that.status = REJECT
          that.value = val
          that.rejectCallbacks.forEach(cb => cb(that.value))
        }
      }
      // 调用传入 MyPromise 内的方法 例:new MyPromise((resolve,reject)=>{})   fn=(resolve,reject)=>{}
      try {
        fn(resolve, reject)
      } catch (error) {
        reject(error)
      }
    }
    // 在原型上添加then方法
    MyPromise.prototype.then = function (onFulfilled, onRejected) {
      const that = this
      // 判断传入的是否为函数
      onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
      onRejected = typeof onRejected === 'function' ? onRejected : r => {
        throw r
      }
    
      //如果 Promise 内部存在异步代码,调用then方法时,此时 promise 内部还是 PENDING 状态,
      将 then 里面的函数添加进回调数组,当异步处理完成后调用 MyPromise 内部的 resolve 或者
      reject 函数
      if (that.status === PENDING) {
        that.resolveCallbacks.push(onFulfilled)
        that.rejectCallbacks.push(onRejected)
      }
    
      // 当 Promise 内部的状态已经为 resolve,则调用 then 里面的函数并传递值
      if (that.status === RESOLVE) {
        onFulfilled(that.value)
      }
    
      // 当 Promise 内部状态为 reject,则调用then里的回调函数并传递值
      if (that.status === REJECT) {
        onRejected(that.value)
      }
    }
    
    // 自己实现的Promise
    new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(1)
        reject(2)
      }, 0)
    }).then(res => {
      console.log(res)
    }, err => {
      console.log(err)
    })
    // MyPromise.resolve(4).then().then(res => console.log(4)) // 透传,尚未实现
    
    

    async和await遇见EventsLoop的注意事项

    • 将await当成什么?
    一般而言,我们可以把
    
    async function f() {
      await p
      console.log('ok')
    }
    简化理解为:
    
    function f() {
      return RESOLVE(p).then(() => {
        console.log('ok')
      })
    }
    『RESOLVE(p)』接近于『Promise.resolve(p)』,不过有微妙而重要的区别:
    p 如果本身已经是 Promise 实例,Promise.resolve 会直接返回 p 而不是产生一个新 promise。
    【个人认为 Promise.resolve 的这一『优化』行为可能是弊大于利的,不过因为已经写进标准了,
    也不太可能修改了。】老版本V8的问题是当 p 是一个已经 settled 的 promise,会进行类似的激进优化,
    导致执行时序与非 settled 的 promise 结果不同。比如把你的 async2 中加入一个 await 语句,
    老版本行为就和新版本一致了。
    

    例题:

    console.log('script start')
    
    async function async1() {
      await async2()
      console.log('async1 end')
    }
    async function async2() {
      console.log('async2 end')
    }
    async1()
    
    setTimeout(function() {
      console.log('setTimeout')
    }, 0)
    
    new Promise(resolve => {
      console.log('Promise')
      resolve()
    })
      .then(function() {
        console.log('promise1')
      })
      .then(function() {
        console.log('promise2')
      })
    
    console.log('script end')
    // script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout
    
    

    解析:

    当await 后面是一个async函数并且执行之后,也就是:
    async function async1() {
      await async2()
      console.log('async1 end')
    }
    async function async2() {
      console.log('async2 end')
    }
    
    由于async2执行后返回一个promise,则相当于
    async function async1() {
       return new Promise((res, rej) => {
         console.log('async2 end')
      }).then(() => {
         console.log('async1 end')
     })
    }
    
    结果一样
    

    null和undefined有何区别

    • null表示有值,但是为空,Number(null) === 1 true
    • undefined表示没有值,还没有定义,Number(undefined) === NaN true

    如何正确判断业务中遇到的undefined值

    • 注意:避免使用==,因为
    null == undefined
    true
    0 == undefined
    false
    '' == undefined
    false
    
    • 正确判断方法
    1、使用====
      if(backgroundAudioManger === undefined) ...
    2、使用typeof
    	if(typeof backgroundAdudioManager == 'undefined')
    

    手写实现call,apply,bind

    • call
    Function.prototype.myCall = function(context) {
      if ( typeof this !== 'function' ) throw new TypeError('Error')
      // 完善部分,如果传入context是个基础类型是无法绑定fn函数的,所以
      
    			if (typeof context === 'object') {
    				context = context || window;
    			} else {
    				context = Object.create(null)
    			}
    
      context = context || window
      // 如果context中有fn则会被覆盖并清除
      // newContext.fn = this
      // 使用Symbol()独一无二数据类型避免fn冲突
      let fn = Symbol('fn')
      context[fn] = this
      let args
      let result
      if ([...arguments][1]) {
        args = [...arguments].slice(1)
        result = newContext.fn(args)
      } else {
        result = newContext.fn()
      }
      delete context[fn]
      return result
    }
    
    function fn () {
      console.log(this.a, this)
    }
    
    const obj = {
      a: 21
    }
    
    fn.myCall(obj)
    
    • apply
    Function.prototype.myApply = function(context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error')
      }
      context = context || window
      context.fn = this
      let result
      // 处理参数和 call 有区别
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
      delete context.fn
      return result
    }
    
    
    • bind
    Function.prototype.myBind = function (context) {
      if (typeof this !== 'function') {
        throw new TypeError('Error')
      }
      const _this = this
      const args = [...arguments].slice(1)
      // 返回一个函数
      return function F() {
        // 因为返回了一个函数,我们可以 new F(),所以需要判断
        if (this instanceof F) {
          return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
      }
    }
    

    手写实现一个new方法

    • new原理那部分,链接到原型,obj = Object.create(Con.prototype),这种方式会比较好
    function _new(fn,...arg){
        let obj = {}
        let con = [].slice.call(arguments)
        obj.__proto__ = con.prototype //链接原型
        const ret = fn.call(obj, ...arg); //改变this的指向
        return ret instanceof Object ? ret : obj;
    }
    
    
    • 参考地址:链接

    Symbol()和Symbol.for()和Symbol.keyFor()区别

    • 参考链接:链接
    • Symbol()返回一个独一无二的标识符类型值
    • Symbol.for(),将传入值生成一个标识符类性值,并将其作为key存入到注册表中
    • Symbol.keyFor(),传入一个Symbol.for()生成的标识符类型值,返回注册表中对应该key的value值(Symbol.for(‘value’))
    • 前端常见面试题总结(一)

    巧用parseFloat避免js浮点数精度转换问题

    (0.1 + 0.2).toFixed(10)
    "0.3000000000"
    parseFloat((0.1 + 0.2).toFixed(10))
    0.3
    

    更简便的手写call

    Function.prototype.myCall = function(context,...args){
      context  = context || window
      const symbol = Symbol()
      context[symbol] = this
      const result = context[symbol](...args)
      delete context[symbol]
      return result
    }
    

    [].shift.call(arguments)是什么原理呢?

    改变绑定的 this:
    
    let a = [1,2,3]
    const myshift = [].shift.bind(a)
    console.log(myshift(a))
    
    
    • Arguments是一个类数组对线,不能直接进行数组想关的操作,使用[].shifit.call(arguments)则可以转化为数组之后进行一些操作
    function fn3 (a, b) {
        console.log(arguments)
        console.log([...arguments])
        console.log([].shift.call(arguments))
    }
    

    前端常见面试题总结(一)

    如果传入的参数为函数,则[].shift.call(arguments)会弹出这个函数
    前端常见面试题总结(一)

    事件代理应用

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>手写函数测试</title>
    </head>
    <body>
    <ul id="ul">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
    </ul>
    <script>
      let ul = document.querySelector('#ul')
      ul.addEventListener('click', (event) => {
        console.log(event.target);
      })
    </script>
    </body>
    </html>
    

    阻止事件冒泡

    • 操作:在子DOM节点的event执行事件上绑定stopPropagation
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>手写函数测试</title>
    </head>
    <body>
    <ul id="ul">
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li id="li">4</li>
      <li>5</li>
    </ul>
    <script>
      let ul = document.querySelector('#ul')
      ul.addEventListener('click', (event) => {
        console.log(event);
      }) // 如果在后面配置项为true, 则先捕获(父节点事件)后冒泡(子节点事件)
      let li = document.querySelector('#li')
      li.addEventListener('click', (event) => {
        console.log(event, '4点击了')
        event.stopPropagation() // 阻止其余捕获
      })
    </script>
    </body>
    </html>
    

    冒泡事件和捕获事件顺序调换的配置

    • 默认false
    <div>
    	外层div
    	<p>内层p</p>
    <div>
    var div = document.querySelector('div');
    var p = document.querySelector('p');
    
    div.addEventListener('click',  function() {console.log("冒泡")}, false);
    div.addEventListener('click',  function() {console.log("捕获")}, true);
    
    点击div, 输出 冒泡,捕获
    点击p, 输出捕获,冒泡
    

    浏览器同源策略要干啥的, 为什么要跨域请求?

    •   当我们在www.a.com这个域下用ajax提交一个请求到www.b.com这个域的时候,默认情况下,浏览器是不允许的,因为违反了浏览器的同源策略。
    • 参考地址:www.cnblogs.com/anai/p/4227…

    跨域解决方案

    • Jsonp
    • Cors白名单
    • document.domain(适用于二级域名相同--->a.test.com 和 b.test.com)
    • postMessage
    • 前端proxy正向代理
    • 后端proxy反向代理

    cookie,localStorage,sessionStorage区别

    • cookie:多为网络请求过程中服务器通过请求信息在浏览器客户端种下,可以用来保存用户登录状态,规避http请求无状态缺陷,缺陷是每一个name对应的value值大小只有4k,可存储量太小,超过会被裁剪,而且每次请求都会携带,造成额外的带宽占用
    • localStorage:除非手动清空,否则用于存在于本地存储中,可在同源标签页下共享,适用于一些长久不变的数据,比如一些电商网站的base64数据,大小约为5兆左右
    • sessionStorage:只在当前窗口下存在,同源标签下不共享,浏览器窗口关闭会被自动清空,适用于需要每次打开都重新登录的网站存储JWT的token数据。大小约为5兆左右
    • 区别:sessionStorage保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据被清空;sessionStorage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 sessionStorage 内容便无法共享;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。除了保存期限的长短不同,SessionStorage的属性和方法与LocalStorage完全一样。
    • 参考:github.com/ljianshu/Bl…

    知道哪些新的缓存方案?

    • IndexDB:浏览器提供的用途类似localStorage的NoSQL型数据库,除非手动清除否则一直存在,类似MongoDB的异步存取,

    • 参考:阮一峰IndexDB入门

    • 参考:简书IndexDB实例

    • ServiceWorker:地址jakearchibald.com/2014/offlin…

    • 可以劫持请求和响应,做缓存,并可以离线运行,不可以在ServiceWorker下定义全局变量

    • ServiceWorker类似的webWorker:www.ruanyifeng.com/blog/2018/0…

    代理的工作原理解析

    • 正向代理和反向代理优缺点
    • proxy正向代理原理:
    • 前端常见面试题总结(一)

    • webpack中的proxy本地代理原理
    • 使用了express框架搭建本web项目服务的时候,引入了http-proxy-middleware的中间件,参考链接www.jianshu.com/p/8fd5d7347…
    • 前端常见面试题总结(一)
    if(res.method == 'OPTIONS') {
    res.statusCode = 204
    res.setHeader('Content-Length', '0')
    res.end()
    } else {
    next()
    }
    
    

    浏览器缓存机制(后端向)

    前端常见面试题总结(一)

    浏览器渲染原理

    参考地址:github.com/ljianshu/Bl…

    • 接收信息解析代码过程:

    前端常见面试题总结(一)

    1. 接收HTML文件,构建DOM树
    2. 接收CSS文件,构建CSSOM树
    3. 接收jS文件(加载js脚本),等到Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。

    • 注意:在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
    • 浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM

    # 性能优化建议:
    • 还有js中操作dom元素的样式时,如果修改多条style,不如把多条style写成一个class,对元素进行添加class的操作。如果是分步添加元素样式,可以将元素赋值给一个变量,避免多次获取一个元素,因为获取元素也会造成回流。
      1. 不要一条一条的修改 DOM 样式,尽量提前设置好 class,后续增加 class,进行批量修改。
      1. 使用 documentFragment 对象在内存里操作 DOM
      1. 不一定说非要用visibility: hidden替换display:none,完全可以把修改频繁的元素先 display: none,修改完之后显示。

    RAF讨论

    • 屏幕每16.6ms才刷新一次,比如这一帧有个人站着,下一帧他应该要迈开左腿,如果都已经下一帧了,浏览器还没渲染出迈开腿的画面,这个人还站着,看着就卡了。然后event loop和渲染这边,我的理解是,在短短16.6ms内,要保证一个tick能执行完并将这个tick里对dom的操作渲染出来(也因此同个tick里的多个dom操作会被合并),这样下一帧才能有迈开腿的画面出现在屏幕上。如果当前的tick要花好久才能执行完,就说40ms吧,那至少两帧都只有人站着的画面,第三帧还剩短短10ms用来重新渲染,如果能渲染出来还好,下一帧就能看,否则下一帧还看不到变化。
    • 所以,假设一个tick里js先往某个
        里添加了几个
      • 子节点,接着还要做一件非常耗时的事情,两件事合在一起要好久才能完成,不如把两件事拆开,放到两个requestAnimationFrame的回调里执行,这样就能先把对dom的修改重新渲染处理,下一帧至少能看到变化了。

      # 前端安全 ## 参考:[XSS攻击和CSRF攻击](https://github.com/dwqs/blog/issues/68) XSS:[https://juejin.im/post/5bad9140e51d450e935c6d64](https://juejin.im/post/5bad9140e51d450e935c6d64)
      CSRF:[https://juejin.im/post/5bc009996fb9a05d0a055192#heading-32](https://juejin.im/post/5bc009996fb9a05d0a055192#heading-32)

      了解webpack原理,手写小型webpack

      流程浅析:参考

      1. createAsset:读取入口文件,通过babylon的parse方法将文件内容字符串转化为AST语法树(包含id,filename,dependencies,code信息)的一种数据结构
      2. createGraph:遍历入门文件AST语法树上的dependencies数组,生成一个新的数组,数组每一项和createAsset方法创建出来的AST语法树非常相像,并且多了一个mapping属性指定文件依赖,数组中将依赖文件根据依赖顺序id递增排列。
      3. bundle:接收graph数组,生成bundle结构的自执行函数
      4. 代码地址:github.com/dykily/simp…

    起源地下载网 » 前端常见面试题总结(一)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元