2022前端面试题一

952 阅读8分钟

题目来源: www.nowcoder.com/discuss/921… 富途社招一面

1. 自定义指令

vue文档: v3.cn.vuejs.org/guide/custo…

注册
// 全局注册
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
  // 当被绑定的元素挂载到 DOM 中时……
  mounted(el) {
    // 聚焦元素
    el.focus()
  }
})

// 组件注册
directives: {
  focus: {
    // 指令的定义
    mounted(el) {
      el.focus()
    }
  }
}

// 使用
<input v-focus />

钩子函数

created
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted

指令参数:

钩子函数一共有两个参数:
el: dom对象
binding: 绑定的参数对象

v-mydirective:[argument]="value"

  • argument对应的是binding.arg
  • value对应的是binding.value,可以是一个对象

 <p v-pin:[direction]="200">

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

2. 跨域

介绍一下跨域原因?

一种安全限制, 浏览器的同源策略造成。为了保证用户信息的安全,防止恶意的网站窃取数据, 同源指的是协议、域名、端口 都要保持一致

解决跨域的方法。
1. jsonp

原理:利用script标签的src属性没有跨域限制, 所有具有src属性的HTML标签都是可以跨域的,只支持get请求

大概步骤:

1. 声明回调
 function jsonpCallback(res) {
   console.log(res)
 }

2. 请求服务器,传回调方法名称给服务器
 const url = 'xxx/api?id=1&cb=jsonpCallback'
 const script = document.createElement('script');
 script.setAttribute('src', url);

3. 服务器返回,使用回调名称包裹数据
 const { cb, id } = ctx.query;
 ctx.body = `${cb}(${JSON.stringify({result})})`;
 
 4. 前端收到请求结果,实际就是字符串,script标签会把返回的结果当做代码执行
2. CORS

不需要cookie: 需要服务端设置Access-Control-Allow-Origin
若要带cookie请求:

// 前端
axios.defaults.withCredentials = true
// 服务端
跨域参数 Access-Control-Allow-Origin不能设置为*
3. 服务器代理

服务器没有跨域问题,一般开发时都是使用服务器代理,常用的如webpack-dev-server

4. postMessage

postMessage主要用于解决跨窗口间的消息传递问题,页面或iframe

a.html:(www.domain1.com/a.html)


<iframe id="iframe" src="http://www.domain2.com/b.html"></iframe>

const iframe = document.getElementById('iframe');
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
// 监听domain2
window.addEventListener('message', function(e) {  
  alert('data from domain2 ---> ' + e.data);
}, false);

b.html:(www.domain2.com/b.html)

window.addEventListener(
  "message",
  function (e) {
    //  收到domain1的数据
    alert("data from domain1 ---> " + e.data);
    // 返回数据给domain1
    window.parent.postMessage("domain2 data", "http://www.domain1.com");
  },
  false
);
5. WebSocket
    // ws->http wss:https
    let ws = new WebSocket('ws://localhost:8888')
    // 连接成功就会执行回调函数
    ws.onopen = function (params) {
      console.log('客户端连接成功')
      // 向服务器发送消息
      ws.send('hello')
    }
    // 必须用属性的方式监听事件,监听函数的参数是事件对象
    ws.onmessage = function (e) {
      console.log('收到服务器响应', e.data)
    }

3. js事件循环的输出题

  • 关于js事件循环的输出题,涉及:async/await、promise、setTimeoout,问了步骤,又问了了解node不?说不熟悉就没有继续出题(关于node的执行输出题)
头条经典面试题async/await、promise、setTimeoout
async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')
//输出
//script start
//async1 start
//async2
//promise1
//script end
//async1 end
//promise2
//setTimeout

常考点:

  1. await是让出线程的标志,会立刻执行后面的表达式, 而await后面的代码会被放入微任务队列中
  2. promise参数方法里面的代码是同步的,回调是异步
  3. 会清空微任务队列后才会执行宏任务
node的事件循环机制

image.png

  • process.nextTick:微任务,但是优先级比setImmediate和setTimeout高
  • setTimeout: 最小为1,浏览器最小为4ms
  • setImmediate: 尽快执行的异步,和延迟为0的setTimeout输出顺序不一定

为什么setImmediate和延迟时间为0的setTimeout执行顺序不一定?

  1. 执行宏任务的时间不一定,如果小于1ms,先执行setImmediate,如果大于1ms,先执行setTimeout
  2. 如果在poll回调(I/O相关)中,顺序一定是setImmediate先执行,因为poll阶段后面是check阶段,setTimeout会在下一次事件循环中执行

参考文章:
segmentfault.com/a/119000002…

4. 经典输出题: ['1', '2', '3'].map(parseInt)

输出: 1 NaN NaN

parseInt(string, radix)解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。注意0表示是10进制,不能解析返回NaN,

['10', '10', '10', '10', '10'].map(parseInt)

// 等同于
parseInt('10', 0) // 10
parseInt('10', 1) // NaN
parseInt('10', 2) // 2
parseInt('10', 3) // 3
parseInt('10', 4) // 4

5. async、defer的区别,

async: 异步下载,下载完后立即执行,会阻塞页面解析 defer: 异步下载,页面解析完后执行

6. 问页面解析渲染的流程,出一道html文件,问解析的流程,问css加载是否阻塞解析、问js执行是否影响到cssom树的构建

image.png

css会阻塞渲染,但不会阻解析
js执行不会影响到cssom树的构建,即使在script在link前面,也要等待cssom构建完成后再执行js,

考点:

  • link标签会阻塞js的执行
  • script标签会触发页面Paint, 遇到script标签,会先绘制前面的标签,先让用户看到

参考文章: www.w3cplus.com/performance…

7. 平时用到的判断类型的方法

1. typeof
  1. 对于基本类型,除null以外,返回正确的结果
  2. 对于引用类型,除function以外,一律返回 object 类型
  3. 对于null,返回object类型。
  4. 对于function返回function类型
typeof null //object 
typeof[] //object 
typeof new Function() // function
2. instanceof

instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型

[] instanceof Array; // true
[] instanceof Object // true
{} instanceof Object // true
isArray

instanceof 操作符的问题在于,如果网页中包含多个框架,那就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。isArray就是为了严格判定JavaScript对象是否为数组而出现的

const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const xArray = window.frames[0].Array;
const arr = new xArray(1, 2, 3);
arr instanceof Array; // false 因为引用地址不同
Array.isArray(arr); // true

// isArray实现
Array.isArray = function (value) {
  return Object.prototype.toString.call(value) === "[object Array]";
};

手写instanceof

function myInstanceof(left, right) {
  if (typeof left !== "object" || left === null) return false;
  let proto = Object.getPrototypeOf(left);
  while (true) {
    if (proto == null) return false;
    if (proto == right.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}
3. constructor
function fun() {}
const f = new fun();
f.constructor === fun // true

new Number(1).constructor === Number // true
[].constructor === Array // true
  1. undefine和null没有constructor
  2. prototype被修改后constructor会丢失,默认为 Object
4. toString

完全正确的判断方法

Object.prototype.toString.call(1// [object Number]
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(true) // [object Boolean]
Object.prototype.toString.call(new Date()) // [object Date]
Object.prototype.toString.call(null) // [object Null]

8. XSS怎么防御

xss: 跨站脚本攻击, 攻击方式是构造可执行的代码,

// 页面内容
<input type="text" value="<%= getParameter("keyword") %>">
<button>搜索</button>
<div>
  您搜索的关键词是:<%= getParameter("keyword") %>
</div>

// 攻击代码
http://xxx/search?keyword="><script>alert('XSS');</script>

类型:

  • 存储型: 恶意代码存在数据库里
  • 反射性: 恶意代码存在 URL 里。
  • dom: 取出和执行恶意代码由浏览器端完成

防御

  1. http-only: 只允许http或https请求读取cookie、JS代码是无法读取
  2. 转义: 使用waf, 避免使用v-html等
  3. csp: 只允许加载指定的脚本及样式, 可以通过head属性Content-Security-Policy开启或设置meta标签属性:http-equiv=Content-Security-Policy

9. CSRF是什么?

跨站请求伪造,利用受害者在被攻击网站的登录凭证,冒充受害者提交操作,

攻击方式: 防御:

  1. SameSite 可以让Cookie不随着跨域请求发送

Strict: 只允许相同站点请求的 Cookie
Lax: 允许部分第三方请求携带 Cookie
None: 都允许

Set-Cookie: flavor=choco; SameSite=None; Secure
  1. Token验证 cookie是发送时自动带上的,而不会主动带上Token,可以在每次发送时主动发送Token

简单token:uid(用户唯一的身份标识) + time(当前时间的时间戳) + sign(签名)。 3. Referer验证, Referer也容易被修改 4. 验证码,效果很好,但是体验较差

10. vue的父子组件模拟双向绑定数据,

  1. v-model和emit
  2. sync与emit
v-model
<MyInput v-model="name" />

<MyInput :value="name" @input="name = $event" />

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

text和textarea元素使用value property 和input事件;
checkbox和radio使用 checked property 和change事件;
select字段将value作为prop并将change作为事件。

model属性可以修改默认值

// MyInput.vue
export default {
    model: {
        prop: 'title', // 默认为value
        event: 'change', // 默认为inut
    },
}
.sync

.sync的方式与实现v-model一样,区别就是抛出的事件名需要是update:myPropName的结构

vue2,3的v-model的区别
  1. vue3 默认prop与event为:modelValueupdate:modelValue;vue2 中则是:valueinput
  2. vue3 中直接通过 v-model 后面参数v-model:msg来指定属性名,并且支持绑定多个 v-model;而 vue2 中通过子组件的model 属性中的prop值和event值来指定属性名和事件名。
// vue2
<MyInput v-model="name" />

<MyInput :value="name" @input="name = $event" />

// vue3
<MyInput v-model:title="name" v-model:content="info" />

<MyInput
    :title="name"
    @update:title="name = $event"
    :content="info"
    @update:content="info = $event"
/>

11. vue的mixins的合并优先级

// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

export default {
  mixins: [myMixins], // 导入
  data() {
    return {};
  },
  methods: {},
};

  1. data:数据在内部会进行递归合并,并在发生冲突时以组件数据优先(组件和混入对象的数据同一个参数名初始值不同,取组件的值)
  2. created、mounted等:同名钩子函数将合并为一个数组,都将被调用且混入对象的钩子将在组件自身钩子之前调用
  3. watch、computed、methods等:值为对象的选项,将被合并为同一个对象,两个对象键名冲突时会覆盖混入对象中的方法

12. vue属性传递

一个组件上绑定的属性,如何实现渲染的时候这些属性绑到组件内的元素上?

应该问的是attrsattrs和listeners,常用于组件层级嵌套比较深的情况

  • $attrs: proprs接收之外的参数
  • $listeners: 包含了父作用域中的事件监听器
  • inheritAttrs: 如果有proprs接收之外的参数,默认会放在根节点上,可以阻止这个默认行为
// A组件
<B :title='test' @foo="foo"/> // 组件传参

// B组件
<C v-bind="$attrs" v-on="$listeners" /> // $attrs对象里面就是porps未接收的参数,$listeners就是绑定的事件

// C组件
<div>{{title}}</div> // porps声明接收后可以获取到title参数,emit可以触发listeners传递的事件

vuex,props,eventbus都是传参的方式

vue3已经移除listeners,事件也放在listeners,事件也放在attrs里面

13 算法题1 判断有序数组是不是另一个的子集

判断有序数组是不是另一个的子集,特别:只要父数组有的元素,子数组有多个也可以算,如:a = [1], b = [1, 1], b也算是a的子集。

function getResult(arr1, arr2) {
  return arr1.every(item => {
    return arr2.includes(item);
  })
}

14. 算法题2 斐波那契数列

// 1、 普通递归,但是有爆栈问题
function fibonacci(n) {
  if (n <= 1) {
    return 1;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 2. 尾递归,解决爆栈问题,有重复计算问题
function fibonacci(n, ac1 = 1, ac2 = 1) {
  if (n <= 1) {
    return ac2;
  }
  return fibonacci(n - 1, ac2, ac1 + ac2);
}

// 3. 实现缓存
function fibonacci(num) {
  let arr = [1, 1];
  while (arr.length < num) {
    arr.push(arr[arr.length - 1] + arr[arr.length - 2])
  }
  return arr;
}
console.log(fibonacci(10));