常见面试题总结(一)

常见面试题总结(一)

什么是原型?

  • 原型分为两种:构造函数原型,称为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
  • 一张图描述清楚构造器想关
  • image.png
    image.png
  • 有些人可能会把 __proto__prototype 搞混淆。从翻译的角度来说,它们都可以叫原型,但是其实是完全不同的两个东西。

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

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

原型链是什么?

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

原型链的作用?

  • 实例访问一个属性的时候,先在自身可遍历属性上寻找,再到原型链上逐层向上寻找,如果找不到会返回undefined
  • stu.proto.proto.proto,直到遇到null,则返回undefined
  • image.png
    image.png
  • 作用:将类的方法定义在原型上,通过这种方式,所有的实例都可以访问到这个方法,并且这个方法只需要占用一份内存,节省内存,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就是定义在了原型上的函数。

<br />

ES6的class本质是什么?

  • 其实,ES6 class 就是构造器的语法糖
  • ES6 的 class 就是构造器,class 上的方法定义在构造器的 prototype 上,因此你也可以理解为什么 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是做什么的?

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

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

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

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

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

如何正确判断业务中遇到的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’))
  • image.png
    image.png

巧用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))
}
image.png
image.png

<br />

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

阻止事件冒泡

  • 操作:在子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, 输出捕获,冒泡

<br />

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

跨域解决方案

  • 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完全一样。
  • 参考:https://github.com/ljianshu/Blog/issues/25

<br />

知道哪些新的缓存方案?

  • IndexDB:浏览器提供的用途类似localStorage的NoSQL型数据库,除非手动清除否则一直存在,类似MongoDB的异步存取,
  • 参考:阮一峰IndexDB入门
  • 参考:简书IndexDB实例

代理的工作原理解析

<br />

  • webpack中的proxy本地代理原理
  • 使用了express框架搭建本web项目服务的时候,引入了http-proxy-middleware的中间件,参考链接https://www.jianshu.com/p/8fd5d7347c57
  • image.png
    image.png

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

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

<br />

性能优化建议:

  • 还有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先往某个<ul>里添加了几个<li>子节点,接着还要做一件非常耗时的事情,两件事合在一起要好久才能完成,不如把两件事拆开,放到两个requestAnimationFrame的回调里执行,这样就能先把对dom的修改重新渲染处理,下一帧至少能看到变化了。

<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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容