常见面试题总结(一)

2021-01-28  本文已影响0人  菩灵

常见面试题总结(一)

什么是原型?

原型之间的关系?

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

function Fa() {}

Fa.constructor
ƒ Function() { [native code] }
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 搞混淆。从翻译的角度来说,它们都可以叫原型,但是其实是完全不同的两个东西。

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

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

原型链是什么?

原型链的作用?

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 在声明的时候就根据上下文确定了
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' ]

<br />

ES6的class本质是什么?

<br />

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}

缺点是只能继承父类实例上的属性,无法继承原型链上的属性。<br />(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的时候不会开辟新的地址,所以当一个实例对象改变了引用属性的值时,另一个对象也会随之改变。<br />(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]

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

Child3.prototype=new Parent3();

换成

Child3.prototype=Parent3.prototype;

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

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]

<br />

Object.create是做什么的?

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。<br />为什么 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。<br />为什么 console.log(p2.hosts) 会报错呢,p2 是直接调用 Page 构造函数的结果,直接调用 page 函数,这个时候 this 指向全局对象,全局对象并没 hosts 属性,因此返回 undefined,往 undefined 上访问 hosts 当然报错。<br />
<br />作者:余腾靖<br />链接:https://juejin.im/post/5e2ff7dce51d4558021a1a4d<br />来源:掘金<br />著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。<br />

proxy是什么?

proxy表单校验实例

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比较

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

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

// 传值
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函数的执行顺序

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 -->
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的注意事项

一般而言,我们可以把

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有何区别

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

null == undefined
true
0 == undefined
false
'' == undefined
false
1、使用====
  if(backgroundAudioManger === undefined) ...
2、使用typeof
    if(typeof backgroundAdudioManager == 'undefined')

手写实现call,apply,bind

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)
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
}

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方法

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()区别

巧用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))

function fn3 (a, b) {
    console.log(arguments)
    console.log([...arguments])
    console.log([].shift.call(arguments))
}
image.pngimage.png

<br />

<br />如果传入的参数为函数,则[].shift.call(arguments)会弹出这个函数<br /> image.pngimage.png

<br />

事件代理应用

<!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>

阻止事件冒泡

<!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>

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

<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, 输出捕获,冒泡

<br />

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

跨域解决方案

cookie,localStorage,sessionStorage区别

<br />

知道哪些新的缓存方案?

代理的工作原理解析

<br />

node服务后台接收预检请求防止报错

if(res.method == 'OPTIONS') {
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
} else {
next()
}

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

[图片上传失败...(image-f02655-1611839965980)]<br />

浏览器渲染原理

参考地址:https://github.com/ljianshu/Blog/issues/51

[图片上传失败...(image-f04011-1611839965980)]

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

<br />

<br />

性能优化建议:

RAF讨论

<br />

前端安全

参考:XSS攻击和CSRF攻击

XSS:https://juejin.im/post/5bad9140e51d450e935c6d64<br />CSRF:https://juejin.im/post/5bc009996fb9a05d0a055192#heading-32<br />

了解webpack原理,手写小型webpack

流程浅析:参考

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

猜你喜欢

热点阅读