有米科技前端实习面筋(凉)

首先是笔试

不定项选择题,三道编程题,三道简答题,几道附加题

选择题

外边距塌陷

外边距塌陷也称为外边距合并,是指两个在正常流中相邻(兄弟或父子关系)的块级元素的外边距,组合在一起变成单个外边距,不过只有上下外边距才会有塌陷,左右外边距不会出现这种问题。

例子:

<style>
        *{
            padding: 0;
            margin: 0;
        }
        .father {
            height: 500px;
            width: 500px;
            background: rgb(184, 184, 167);
        }
        .son {
            width: 200px;
            height: 200px;
            background: rgb(199, 20, 20);
            margin-top: 100px;
        }</style>
<body>
<div class="father">
<div class="son">子盒子设置上外边距,父盒子会随着子盒子塌陷,嵌套块元素垂直外边距合并</div>
</div>
</body>

 如下图所示,是希望出现的效果,然而实际情况是父盒子会随着子盒子一起下来

 

 

 

 

实际效果:

 

 

 

当外边距塌陷时,外边距之间的计算方式是怎样的?
1.两个都是正数,取较大的值
2.两个都是负数,取绝对值较大的值
3.一正一负,取两个值得和

解决方法:

1:为父盒子设置1像素的上边框,颜色一样,但是会撑大父盒子,所以父盒子高度减1

 

/* 解决办法 1:为父盒子设置1像素的上边框,颜色一样,但是会撑大父盒子,所以父盒子高度减1*/
        .father {
            border-top: 1px solid rgb(184, 184, 167);
            height: 499px;
        }

 

 2:为子盒子设置1像素的上内边距,也会撑大子盒子,所以子盒子高度减1

 

/* 解决方法 2:为子盒子设置1像素的上内边距,也会撑大子盒子,所以子盒子高度减1 */
        .son {
            padding-top: 1px;
            height: 199px;
        }

 

 3:父盒子设置overflow:hidden ,触发bfc ,不会撑大盒子

/* 解决方法 3:父盒子设置overflow:hidden ,触发bfc ,不会撑大盒子*/
        .father {
            overflow: hidden;
        }

 

 

 

javascript线程和GUI线程

GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中,等到JS引擎空闲时,立即被执行。

浏览器多进程和js单线程

opacity visibility display

opacity 用来设置透明度
display 定义建立布局时元素生成的显示框类型
visibility 用来设置元素是否可见。

 

 

 

async和defer

1. script 没有 defer 和 async

  会停止(阻塞)dom 树构建,立即加载,并执行脚本

2. script 带 async

  不会停止(阻塞)dom 树构建,立即异步加载,加载好后立即执行

3. script 带 defer

  不会停止(阻塞)dom 树构建,立即异步加载。加载好后,如果 dom 树还没构建好,则先等 dom 树解析好再执行;如果 dom 树已经准备好,则立即执行。

git

BFC

 

三道编程题

1.手写instanceof

instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置。

实现思路:

首先 instanceof 左侧必须是对象, 才能找到它的原型链

instanceof 右侧必须是函数, 函数才会prototype属性

迭代 , 左侧对象的原型不等于右侧的 prototype时, 沿着原型链重新赋值左侧

// [1,2,3] instanceof Array ---- true

// L instanceof R
// 变量R的原型 存在于 变量L的原型链上
function instance_of(L,R){    
    // 验证如果为基本数据类型,就直接返回false
    const baseType = ['string', 'number','boolean','undefined','symbol']
    if(baseType.includes(typeof(L))) { return false }
    
    let RP  = R.prototype;  //取 R 的显示原型
    L = L.__proto__;       //取 L 的隐式原型
    while(true){           // 无线循环的写法(也可以使 for(;;) )
        if(L === null){    //找到最顶层
            return false;
        }
        if(L === RP){       //严格相等
            return true;
        }
        L = L.__proto__;  //没找到继续向上一层原型链查找
    }
}

 

2.有两个数组,求它们的交集

3.实现数组的flat方法

来自https://segmentfault.com/a/1190000021366004Array.prototype.flat()数组扁平化,数组拉平,数组降维

const animals = ["🐷", ["🐶", "🐂"], ["🐎", ["🐑", ["🐲"]], "🐛"]];

// 不传参数时,默认“拉平”一层
animals.flat();
// ["🐷", "🐶", "🐂", "🐎", ["🐑", ["🐲"]], "🐛"]

// 传入一个整数参数,整数即“拉平”的层数
animals.flat(2);
// ["🐷", "🐶", "🐂", "🐎", "🐑", ["🐲"], "🐛"]

// Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
animals.flat(Infinity);
// ["🐷", "🐶", "🐂", "🐎", "🐑", "🐲", "🐛"]

// 传入 <=0 的整数将返回原数组,不“拉平”
animals.flat(0);
animals.flat(-10);
// ["🐷", ["🐶", "🐂"], ["🐎", ["🐑", ["🐲"]], "🐛"]];

// 如果原数组有空位,flat()方法会跳过空位。
["🐷", "🐶", "🐂", "🐎",,].flat();
// ["🐷", "🐶", "🐂", "🐎"]

 

Array.prototype.flat() 特性总结

  • Array.prototype.flat() 用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
  • 不传参数时,默认“拉平”一层,可以传入一个整数,表示想要“拉平”的层数。
  • 传入 <=0 的整数将返回原数组,不“拉平”
  • Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
  • 如果原数组有空位,Array.prototype.flat() 会跳过空位。

实现思路

如何实现呢,思路非常简单:实现一个有数组拍平功能的 flat 函数,我们要做的就是在数组中找到是数组类型的元素,然后将他们展开。这就是实现数组拍平 flat 方法的关键思路。

有了思路,我们就需要解决实现这个思路需要克服的困难:

  • 第一个要解决的就是遍历数组的每一个元素;
  • 第二个要解决的就是判断元素是否是数组;
  • 第三个要解决的就是将数组的元素展开一层;

遍历数组的方案

遍历数组并取得数组元素的方法非常之多,包括且不限于下面几种

  • for 循环
  • for...of
  • for...in
  • forEach()
  • entries()
  • keys()
  • values()
  • reduce()
  • map()
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
// 遍历数组的方法有太多,本文只枚举常用的几种
// for 循环
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
// for...of
for (let value of arr) {
  console.log(value);
}
// for...in
for (let i in arr) {
  console.log(arr[i]);
}
// forEach 循环
arr.forEach(value => {
  console.log(value);
});
// entries()
for (let [index, value] of arr.entries()) {
  console.log(value);
}
// keys()
for (let index of arr.keys()) {
  console.log(arr[index]);
}
// values()
for (let value of arr.values()) {
  console.log(value);
}
// reduce()
arr.reduce((pre, cur) => {
  console.log(cur);
}, []);
// map()
arr.map(value => console.log(value));

 

只要是能够遍历数组取到数组中每一个元素的方法,都是一种可行的解决方案。

判断元素是数组的方案

  • instanceof
  • constructor
  • Object.prototype.toString
  • isArray
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
arr instanceof Array
// true
arr.constructor === Array
// true
Object.prototype.toString.call(arr) === '[object Array]'
// true
Array.isArray(arr)
// true

 

说明

  • instanceof 操作符是假定只有一种全局环境,如果网页中包含多个框架,多个全局环境,如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。(所以在这种情况下会不准确)
  • typeof 操作符对数组取类型将返回 object
  • 因为 constructor 可以被重写,所以不能确保一定是数组。

将数组的元素展开一层的方案

  • 扩展运算符 + concat

    concat() 方法用于合并两个或多个数组,在拼接的过程中加上扩展运算符会展开一层数组。详细见下面的代码。

  • concat + apply

主要是利用 apply 在绑定作用域时,传入的第二个参数是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。也就是在调用 apply 函数的过程中,会将传入的数组一个一个的传入到要执行的函数中,也就是相当对数组进行了一层的展开。

  • toString + split

不推荐使用 toString + split 方法,因为操作字符串是和危险的事情,如果数组中的元素所有都是数字的话,toString + split 是可行的,并且是一步搞定。

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
// 扩展运算符 + concat
[].concat(...arr)
// [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

// concat + apply
[].concat.apply([], arr);
// [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

// toString  + split
const arr2 =[1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]]]
arr2.toString().split(',').map(v=>parseInt(v))
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3]

 总结完要解决的三大困难,那我们就可以非常轻松的实现一版数组拍平 flat 函数了。

const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];
// concat + 递归
function flat(arr) {
  let arrResult = [];
  arr.forEach(item => {
    if (Array.isArray(item)) {
      arrResult = arrResult.concat(arguments.callee(item));   // 递归
      // 或者用扩展运算符
      // arrResult.push(...arguments.callee(item));
    } else {
      arrResult.push(item);
    }
  });
  return arrResult;
}
flat(arr)
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

 

简答题

1.说一下commonjs和es6的模块机制

2.说一下前端本地缓存以及它们的区别

3.说一下发布订阅模式和监听者模式的区别,还有它们的作用

 

角色角度来看,订阅发布模式需要三种角色,发布者、事件中心和订阅者。二观察者模式需要两种角色,目标和观察者,无事件中心负责通信。

从耦合度上来看,订阅发布模式是一个事件中心调度模式,订阅者和发布者是没有直接关联的,通过事件中心进行关联,两者是解耦的。而观察者模式中目标和观察者是直接关联的,耦合在一起(有些观念说观察者是解耦,解耦的是业务代码,不是目标和观察者本身)。

订阅发布模式优点

  • 灵活

    由于订阅发布模式的发布者和订阅者是解耦的,只要引入订阅发布模式的事件中心,无论在何处都可以发布订阅。同时订阅发布者相互之间不影响。

订阅发布模式在使用不当的情况下,容易造成数据流混乱,所以才有了 React 提出的单项数据流思想,就是为了解决数据流混乱的问题。

订阅发布模式缺点

  • 容易导致代码不好维护

    灵活是有点,同时也是缺点,使用不当就会造成数据流混乱,导致代码不好维护。

  • 性能消耗更大

    订阅发布模式需要维护事件列队,订阅的事件越多,内存消耗越大。

观察者模式优点

  • 响应式

    目标变化就会通知观察者,这是观察者最大的有点,也是因为这个优点,观察者模式在前端才会这么出名。

观察者模式缺点

  • 不灵活

    相比订阅发布模式,由于目标和观察者是耦合在一起的,所以观察者模式需要同时引入目标和观察者才能达到响应式的效果。而订阅发布模式只需要引入事件中心,订阅者和发布者可以不再一处。

附加题

XSS与 CSRF 攻击原理以及防范

XSS

XSS攻击原理:

跨站脚本攻击,攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些代码,嵌入到web页面中去,从而盗取用户资料、利用用户身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。

XSS防范:

不信任任何客户端提交的数据,只要是客户端提交的数据就应该先进行相应的过滤处理然后方可进行下一步的操作。

CSRF

CSRF攻击原理:

CSRF,跨站请求伪造,攻击方伪装用户身份发送请求从而窃取信息或者破坏系统。例如:

用户访问A网站登陆并生成了cookie,再访问B网站,如果A网站存在CSRF漏洞,此时B网站给A网站的请求(此时相当于是用户访问),A网站会认为是用户发的请求,从而B网站就成功伪装了你的身份,因此叫跨站请求伪造。

CSRF防范:

1、重要数据交互采用POST进行接收,当然是用POST也不是万能的,伪造一个form表单即可破解

2、使用验证码,只要是涉及到数据交互就先进行验证码验证,这个方法可以完全解决CSRF。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。

3、验证HTTP Referer字段,该字段记录了此次HTTP请求的来源地址,最常见的应用是图片防盗链。PHP中可以采用Apache URL重写规则进行防御

4、为每个表单添加token令牌并验证

说一下set,map,weakset,weakmap的区别

1.Set

Set 是一种叫做集合的数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构。

2.WeakSet

WeakSet跟Set区别:

    WeakSet 的成员只能是对象,而不能是其他类型的值,而 Set 对象都可以
    WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

3.Map

Map 是一种叫做字典的数据结构,类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

4.WeakMap

WeakMap跟Map区别:

    WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
    WeakMap的键名所指向的对象,不计入垃圾回收机制

git rebase和git merge的区别

JS事件循环机制

 

面试

基本就着笔试题目来拓展问

下面是除了在笔试之外出现的问题

1.知道js怎么实现类吗?

我没用过,所以面试官还问我java里class和interface的区别,继承,重载,怎么样只允许子类继承父类,

1、JS中通过prototype实现原型继承
2、JS对象可以通过对象冒充,实现多重继承
3、Object类是所有Js类的基类
4、通过function对对象进行封装
5、通过使用arguments实现参数重载
6、ES6语法糖可以直接定义类class,继承对象extends

2.git 的常用命令

(就项目来问)

登陆页有没有使用鉴权,对请求使用鉴权

后端koa怎么实现jwt鉴权

说说浏览器缓存保存了什么数据

 

posted @ 2021-06-21 23:32  xiaoguo185  阅读(481)  评论(0)    收藏  举报