前言
好的,初生牛犊不怕虎,前端小白面字节。你问我咋这么自信的,哈哈哈一切都是内推的机缘巧合。20下午体验了字节实习生一面,总体感受是面试官人超好,不会的题目还会尽心引导,然后的都在尽量挖掘我的闪光点(实话实说,太菜了要加强学习)。举个栗子来说就是,字节的面试体验就是无论你的回答是好还是坏,都会让你觉得你能过。哈哈哈哈哈哈要不是我知道自己不太行,在面试官的 ‘温柔’ 下,我还以为我过了哈哈哈。良好体验+学习成长,这是意义所在!
现在介绍一下部分面试题(改)
面试题
变量提升+原型链
这题比较基础,又是很经典的题,考察的内容很多,原型链,声明提升,运算符优先级等等
// 1 构造函数 内部变量指向匿名函数
function Fun() {
getName = function () {
console.log(1);
};
return this;
}
// 2 定义全局变量 getName
var getName;
// 3 定义一个getName 函数 函数声明提升
function getName() {
console.log(5);
}
// 4 给构造函数定义一个属性 getName
Fun.getName = function () {
console.log(2);
};
// 5 在构造函数的原型上定义 一个getName方法
Fun.prototype.getName = function () {
console.log(3);
};
// 6 getName变量指向一个匿名函数
getName = function () {
console.log(4);
};
getName();//4
Fun.getName()// 2
Fun().getName(); // 1
getName(); // 1
new Fun.getName();//2
new Fun().getName();//3
new new Fun().getName();//3
复制代码
分析一下题目:
- getName(); // 4 代码块2 变量声明提升到最前 getName (实际为undefined) ;代码块3 定义函数声明提升 getName函数 ,函数声明的优先级高于变量声明,所以把代码块2 覆盖应当输出 5,那为什么实际上输出 4 呢? 因为代码块 6 把又把变量getName指向匿名函数,所以再次覆盖了,举个栗子理解下。
var getName ;
var getName = function () {
console.log(5);
}
console.log(getName); // [Function: getName]
getName() // 5
---------------
function getName() {
console.log(5);
}
var getName = 1;
console.log(getName); // 1
getName() // 报错
复制代码
在上述栗子中,这叫覆盖,重新赋值。
-
Fun.getName(); // 2 直接调用代码块 4 构造函数的静态方法 输出结果 为 2
-
Fun().getName(); // 1 调用构造函数Fun(),然后给全局的getName()重新赋值,函数内部的 return this 把整个函数内容返回到了上层作用域(window) ,所以等于全局上的getName() 已经修改为 输出 1
-
getName(); // 1 上一步已经修改全局 ,输出 1
-
new Fun.getName(); //2 .的执行顺序高,先执行 Fun.getName() 后返回 2 ,再被 new 进行实例化所以 new的过程就相当于 把Fun.getName()执行了一遍输出2,然后返回了一个空的实例(new函数调用时,会执行这个函数,所以打印输出2)
-
new Fun().getName(); //3 这里 new 调用构造函数, 所以new关键字最后会生成一个实例对象fun.getName(),然后顺着原型链从自身到__proto__找属性。 所以找到 原型链上:
Fun.prototype.getName = function () {
console.log(3);
};
复制代码
- new new Fun().getName(); //3 这里相当于 new ((new Fun()).getName)() => new (fun.getName)() => new (Fun.prototype.getName(){…}) 返回 3
promise.all手写
这题对于大佬来说吧其实不难,不过呢面试的时候我才看懂手写.then的链式调用后面就没继续看了….在上一篇文章里(悔不当初….)
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
复制代码
手写深拷贝
何为深拷贝,浅拷贝?
浅拷贝
-
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 借用大佬的图片进行理解
深拷贝
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
- 借用大佬的图片进行理解
实现
浅拷贝实现
- 简单赋值
var arr = [1,2,3];
var newArr = arr;
newArr[1] = "二";
console.log(arr); // [ 1, '二', 3 ]
console.log(newArr); // [ 1, '二', 3 ]
console.log(arr==newArr); // true
console.log(arr===newArr); // true
复制代码
- Object.assign():把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var a = { age: 18, name: 'yy', info: { address: 'beijing', sports: 'basketball' } };
var b = Object.assign(a);
b.info.address = 'shenzhen';
b.age = 22
console.log(a); // {age: 22,name: 'yy',info: { address: 'shenzhen', sports: 'basketball' } }
console.log(b); // {age: 22,name: 'yy',info: { address: 'shenzhen', sports: 'basketball' } }b
复制代码
- 数组方法 concat 和 slice
当数组内为基本数据类型时,则是a数组变,b数组不变,但是实际上还是浅拷贝,因为当数组内部有引用类型时,拷贝出来数组中的对象还是共享同一内存地址。
let a = [1,2,3,4];
let b = a.concat();
a[0] = 0;
console.log(a); // [ 0, 2, 3, 4 ]
console.log(b); // [ 1, 2, 3, 4 ]
let a1 = [1,2,[3,4],{name:'HH'}];
let b1 = a1.concat();
a1[3].name = 'YY';
a1[0] = 0;
console.log(a1); // [ 0, 2, [ 3, 4 ], { name: 'YY' } ]
console.log(b1); // [ 1, 2, [ 3, 4 ], { name: 'YY' } ]
let a2 = [1,2,3,4];
let b2 = a2.slice();
a2[0] = 0;
console.log(a2); // [ 0, 2, 3, 4 ]
console.log(b2); // [ 1, 2, 3, 4 ]
let a3 = [1, 3, {
name: ' HH'
}];
let a4 = a3.slice();
a3[2].name = 'YY'
a3[0] = 0
console.log(a3); // [ 0, 3, { name: 'YY' } ]
console.log(a4); // [ 1, 3, { name: 'YY' } ]
复制代码
- 扩展运算符
let obj1 = { name: 'HH', address:{x:'beijing',y:'wuhan'}}
let obj2= {... obj1}
obj1.address.x = 'shenzhen';
obj1.name = 'YY'
console.log(obj1) // { name: 'YY', address: { x: 'shenzhen', y: 'wuhan' } }
console.log(obj2) // { name: 'HH', address: { x: 'shenzhen', y: 'wuhan' } }
复制代码
深拷贝实现
- JSON.parse(JSON.stringify(obj))可以对数组或对象深拷贝,但它的使用也是有局限性的,不能深拷贝含有undefined、function、symbol值的对象
- 函数库lodash的_.cloneDeep方法
手写实现
- 原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
- 简单版
- 如果是基本类型,无需继续拷贝,直接返回
- 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
function deepClone(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}
复制代码
2.终极版
基于简单版的基础上,考虑了内置对象比如 Date、RegExp 等对象和函数以及解决了循环引用的问题。
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;
function deepClone(target, map = new WeakMap()) {
if (map.get(target)) {
return target;
}
// 获取当前值的构造函数:获取它的类型
let constructor = target.constructor;
// 检测当前对象target是否与正则、日期格式对象匹配
if (/^(RegExp|Date)$/i.test(constructor.name)) {
// 创建一个新的特殊对象(正则类/日期类)的实例
return new constructor(target);
}
if (isObject(target)) {
map.set(target, true); // 为循环引用的对象做标记
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
}
复制代码
CSS 盒模型
所有HTML元素可以看作盒子,CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。
盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。
- Margin(外边距) – 清除边框外的区域,外边距是透明的。
- Border(边框) – 围绕在内边距和内容外的边框。
- Padding(内边距) – 清除内容周围的区域,内边距是透明的。
- Content(内容) – 盒子的内容,显示文本和图像。
盒模型是什么?
页面就是由一个个盒模型堆砌起来的,每个HTML元素都可以叫做盒模型,盒模型由外而内包括:边距(margin)、边框(border)、填充(padding)、内容(content)。它在页面中所占的实际宽度是margin + border + paddint + content 的宽度相加。
一般来说:
当设置了一个宽度后,只是设置内容区域的宽度和高度,下面这个栗子
div {
width: 300px;
border: 25px solid green;
padding: 25px;
margin: 25px;
}
复制代码
实际盒子的大小为:
总元素的宽度(450px)=宽度(300px)+左填充(25px)+右填充(25px)+左边框(25px)+右边框(25px)+左边距(25px)+右边距(25px)
那么不一般的情况的?
W3C标准盒模型和IE的盒模型的区别
W3C标准盒模型 : width指content部分的宽度
IE盒模型 : width表示content+padding+border这三个部分的宽度
结合实际的话,IE盒模型更符合日常生活,盒子的宽度应该包括content+padding+border,所以在W3C在CSS3中也增加了box-sizing属性,包含两个属性content-box 和 border-box。
- box-sizing: content-box 是W3C盒子模型
- box-sizing: border-box 是IE盒子模型
BFC的了解
BFC的基本概念
BFC——块级格式化上下文。它是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。通俗的讲,就是一个特殊的块,内部有自己的布局方式,不受外边元素的影响。
原理
- BFC内部的盒子,会在垂直方向,一个接一个地放置。。
- BFC就是页面上的一个独立容器,容器里面的子元素不会影响到外面的元素,外边的也不会影响里边的。
- BFC的区域不会与float box重叠。
- 计算BFC的高度时,浮动元素也被计算在内
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠。
如何创建BFC
- 1、float的值不是none。
- 2、position的值不是static或者relative。
- 3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex
- 4、overflow的值不是visible
使用场景
- 1.利用BFC避免margin重叠。
- 2.清除浮动
- 3.自适应布局
具体实现有兴趣的小伙伴可以。
其他面试题
- 项目,为什么要做这个,有什么难点
- CSS flex布局
- 大数相加
- for in 和 for of
- …
总结
字节面试官给人的感觉就是温文尔雅,如沐春风,只是我还是太菜了,还得加强学习,总的来说这是一次非常宝贵的学习经历,学习到很多。面试时不要慌,不要冷场,主动把面试官引导到自己了解的知识点,惭愧当时我手写就吓傻了话都不会说了。相信肯定有很多优秀的小伙伴想去字节而不敢去面,大家不要怕,在下前端小白也是一枚字节‘落榜生’了,字节对实习生的要求主要还是基础能力鸭,个人感觉字节对于年轻人还是有有好大的信任和支持的,大家冲鸭!
还有就是把一个好的idea然后把它做成项目展示给面试官,从为什么做,从何何来,遇到的难点等问题一 一介绍给面试官,这会体现出独特的属于你个人的魅力,哈哈哈说不定就….