如今前端工程师的技术要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在中大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。接下来,我将列举我面试时以及自认为比较重要的CSS部分、JS部分常见手写原理题!
CSS部分
经典Flex布局
如今Flex布局不管是移动端还是PC端的应用已经非常广泛了,下面我列举几个平时项目中非常常见的几个需求。以下例子我们都以Vue项目为例~
flex布局均匀分布后换行问题
需求一:ul下有多个li,每三个li排一列,多余的换行显示。
很显然,绝大部分的小伙伴都会使用Flex布局,很显然会出现一个问题就是如果li是3的倍数的话就能正常显示,若不是的话,布局就不是产品经理满意的结果。
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
复制代码
解决方案:
我们在ul的底部新增li,个数为数组总长度%3的余数即可。
两栏布局:左右两栏,左边固定,右边自适应
效果图
第一种方式 — 浮动
HTML部分:
第二种方式 — flex
HTML部分:
注意:flex: 0 0 200px是flex: flex-grow flex-shrink flex-basis的简写
复制代码
第三种方式 — position
HTML部分:
第四种方式 — position again
HTML部分:
三栏布局
三栏布局: 中间列自适应宽度,旁边两侧固定宽度
效果图
第一种方式 — 定位
HTML部分:
第二种方式 — flex布局
HTML部分:
第三种方式 — 浮动原理
HTML部分:
圣杯布局
圣杯布局: 中间的优先渲染,独立的左中右结构
具体实现圣杯布局的步骤:
让左右浮动在一行显示,相对定位
让中间模块的middle宽度为100%
让左边的色块移动到middle前面,margin-left:-100%
让右边的色块移动到middle的后面,margin-left:-宽度
给三个小块的父元素加一个内填充的属性padding,为的是填充挤到中间
给左边的块移动到左边left:-200px, 给右边的块移动到右边right:-200px
效果图
HTML部分:
双飞翼布局
具体实现双飞翼布局的步骤:
给左,中,右 加浮动,在一行显示
给middle宽度为100%
让左边的模块移动middle的左边 margin-left:-100%
让右边的模块移动middle的右边 margin-left:-自己宽度
给middle里面的容器添加外间距 margin: 左右
效果:
html部分
box.style.position = 'absolute';
box.style.left = (winW - boxW) / 2 + 'px';
box.style.top = (winH - boxH) / 2 + 'px';
复制代码第四种:table-cell
body {
display: table-cell;
vertical-align: middle;
text-align: center;
}
复制代码
JS 部分
统计网页中出现的标签
实现步骤:
获取所有的DOM节点
NodeList集合转化为数组
获取数组每个元素的标签名
去重
new Set([…document.querySelectorAll(’*’)].map(ele=>ele.tagName)).size
复制代码
JS深浅拷贝
对象深浅拷贝,是面试常见的面试题之一。
原对象:
let obj = {
a: 100,
b: [100, 200, 300],
c: {
x: 10
},
d: /^\d+$/
}
复制代码
浅克隆
浅克隆 只克隆第一层
方法一:
let obj2 = {…obj};
复制代码方法二:
let obj2 = {};
for(let key in obj) {
if(!obj.hasOwnProperty(key)) break;
obj2[key] = obj[key];
}
复制代码
深克隆
注意:在函数、日期、正则表达式时,JSON.stringify时,都会被转换成对象{}
方法一:
let obj3 = JSON.parse(JSON.stringify(obj));
复制代码方法二:
function deepClone(obj) {
// 过滤一些特殊情况
if(obj === null) return null;
if(typeof obj !== “object”) return obj;
if (typeof window !== ‘undefined’ && window.JSON) { // 浏览器环境下 并支持window.JSON 则使用 JSON
return JSON.parse(JSON.stringify(obj));
}
if(obj instanceof RegExp) { // 正则
return new RegExp(obj);
}
if(obj instanceof Date) { // 日期
return new Date(obj);
}
// let newObj = {}
// let newObj = new Object()
let newObj = new obj.constructor; // 不直接创建空对象的目的:克隆的结果和之前保持所属类 =》 即能克隆普通对象,又能克隆某个实例对象
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
// let newObj = obj.constructor === Array ? [] : {};
//for(let key in obj) {
// newObj[key] = typeof obj[key] === ‘object’ ? deepCopy(obj[key]) : //obj[key];
//}
return newObj;
}
复制代码原生Ajax
一个完整的 ajax 请求一般包括以下步骤:
实例化 XMLHttpRequest 对象
连接服务器
发送请求
介绍
function ajax(options) {
let method = options.method || ‘GET’, // 不传则默认为GET请求
params = options.params, // GET请求携带的参数
data = options.data, // POST请求传递的参数
url = options.url + (params ? ‘?’ + Object.keys(params).map(key => key + ‘=’ + params[key]).join(’&’) : ‘’),
async = options.async === false ? false : true,
success = options.success,
headers = options.headers;
let xhr;
// 创建xhr对象
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject(‘Microsoft.XMLHTTP’);
}
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
success && success(xhr.responseText);
}
}
xhr.open(method, url, async);
if(headers) {
Object.keys(Headers).forEach(key => xhr.setRequestHeader(key, headers[key]))
}
method === ‘GET’ ? xhr.send() : xhr.send(data)
}
复制代码注意:IE5、6不兼容XMLHttpRequest,所以要使用ActiveXObject()对象,并传入 ‘Microsoft.XMLHTTP’,达到兼容目的。
readyState的五种状态详解:
0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用了
防抖和节流
如今前端界面效果越来越复杂,有一些频繁操作会导致页面性能和用户体验度低。像:输入框搜索会频繁调端口接口、放大缩小窗口等。
防抖 - debounce 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
const debounce = (fn, delay) => {
let timer = null;
return (…args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
复制代码
节流 - throttle 当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
const throttle = (fn, delay = 500) => {
let flag = true;
return (…args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
复制代码解析 URL 参数
function parseParam(url) {
// 将浏览器地址中 ‘?’ 后面的字符串取出来
const paramsStr = /.+?(.+)KaTeX parse error: Expected 'EOF', got '&' at position 34: … // 将截取的字符串以 ‘&̲’ 分割后存到数组中 …/.test(val) ? parseFloat(val) : val;
// 判断存放对象中是否存在key属性
if (paramsObj.hasOwnProperty(key)) {
// 存在的话就存放一个数组
paramsObj[key] = [].concat(paramsObj[key], val);
} else {
// 不存在就存放一个对象
paramsObj[key] = val;
}
} else {
// 没有value的情况
paramsObj[param] = true;
}
})
return paramsObj;
}
复制代码let url = ‘https://www.baidu.com?username=%22tmc%22&password=%22123456%22&dutiy=%E5%89%8D%E7%AB%AF%E6%94%BB%E5%9F%8E%E7%8B%AE&flag’;
console.log(parseParam(url))
{ username: ‘“tmc”’,
password: ‘“123456”’,
dutiy: ‘前端攻城狮’,
flag: true
}
复制代码Jsonp的原理
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
window[cb] = function (data) { // 声明全局变量
resolve(data)
document.body.removeChild(script)
}
params = {…params, cb}
let arrs = []
for(let key in params) {
arrs.push(${key}=${params[key]}
)
}
let script = document.createElement(‘script’)
script.src = ${url}?${arrs.join('&')}
document.body.appendChild(script)
})
}
复制代码jsonp的缺点:
只能发送Get请求 不支持post put delete
不安全 xss攻击
apply的原理
apply 的实现原理和 call 的实现原理差不多,只是参数形式不一样。— 数组
Function.prototype.apply = function(content = window) {
content.fn = this;
let result;
// 判断是否有第二个参数
if(arguments[1]) {
result = content.fn(…arguments[1]);
} else {
result = content.fn();
}
delete content.fn;
return result;
}
复制代码注意:当apply传入的第一个参数为null时,函数体内的this会指向window。
bind的原理
bind 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
Function.prototype.bind = function(content) {
if(typeof this != ‘function’) {
throw Error(‘not a function’);
}
let _this = this;
let args = […arguments].slice(1);
return function F() {
// 判断是否被当做构造函数使用
if(this instanceof F) {
return _this.apply(this, args.concat([…arguments]))
}
return _this.apply(content, args.concat([…arguments]))
}
}
复制代码call的原理
call语法:fun.call(thisArg, arg1, arg2, arg3, …)
call 的核心原理:
将函数设为对象的属性
执行和删除这个函数
指定this到函数并传入给定参数执行函数
如果不传参数,默认指向window
Function.prototype.call2 = function(content = window) {
// 判断是否是underfine和null
// if(typeof content === ‘undefined’ || typeof content === null){
// content = window
// }
content.fn = this;
let args = […arguments].slice(1);
let result = content.fn(…args);
delete content.fn;
return result;
}