面试前端面试常考手写题 - JavaScript - CSS

Posted YK菌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试前端面试常考手写题 - JavaScript - CSS相关的知识,希望对你有一定的参考价值。

持续更新中…

1. CSS布局

1.1 盒子居中对齐

1.2 两栏布局

1.3 三栏布局

双飞翼

圣杯

1.4 品字布局

2. JS手写函数

2.1 手写原生

Promise

/**
 * Promise构造函数
 * @param {*} executor 执行器函数(同步执行)(resolve, reject) => {}
 */
function Promise(executor) {

  const self = this; // 保存当前实例对象的this的值
  // 添加属性
  self.PromiseState = PENDING // 给promise对象指定status属性,初始值为pending
  self.PromiseResult = null // 给promise对象指定一个用于存储结果数据的属性
  self.callbacks = [] // 存的是对象 每个元素的结构:{onResolved() {}, onRejected() {}}

  /**
   * executor内部定义成功时调用的函数
   * @param {*} value 成功的值
   * @returns 
   */
  function resolve(value) {
    // 如果当前状态不是pending,直接结束
    if (self.PromiseState !== PENDING) return
    // 1. 修改对象的状态(promiseState)为 fulfilled
    self.PromiseState = RESOLVED 
    // 2. 设置对象结果值(promiseResult)为 value
    self.PromiseResult = value
    // 如果有待执行的callback函数,立即【异步】执行回调函数onResolved
    if (self.callbacks.length > 0) {
      setTimeout(() => { // 放入队列中执行所有成功的回调
        self.callbacks.forEach(callbacksObj => {
          callbacksObj.onResolved(value)
        })
      }, 0)
    }
  }
  
  /**
   * executor内部定义失败时调用的函数
   * @param {*} reason 失败的原因
   * @returns 
   */
  function reject(reason) {
    // 如果当前状态不是pending,直接结束
    if (self.PromiseState !== PENDING) return
    // 1. 修改对象的状态(promiseState)为 rejected
    self.PromiseState = REJECTED
    // 2. 设置对象结果值(promiseResult)为 reason
    self.PromiseResult = reason
    // 如果有待执行的callback函数,立即【异步】执行回调函数onRejected
    if (self.callbacks.length > 0) {
      setTimeout(() => { // 放入队列中执行所有失败的回调
        self.callbacks.forEach(callbacksObj => {
          callbacksObj.onRejected(reason)
        })
      }, 0)
    }
  }
  
  // 立即【同步】执行executor函数
  try {
    executor(resolve, reject)
  } catch(error) { // 如果执行器抛出异常,promise对象变成rejected状态
    reject(error)
  }
}

Promise.all

接收的是一个promise数组promises
当传入的所有promise都成功,则返回一个成功的promise,值为所有成功promise的值的数组【注意顺序】
当传入的promises中有一个不成功,则返回一个失败的promise,其值为这个失败的promise的值

思路
首先创建一个结果数组,再创建要给计数变量

Promise.all = function(promises){
	return new Promise((resolve, reject)=>{
		let count = 0
		const values = []
		for(let i = 0; i < promises.length; i++){
			Promise.resolve(promise[i]).then(value => {
				count++
				values[i] = value
				if(count === promises.length){
					resolve(values)
				}
			}, reason => { 
				reject(reason) 
			})
		}
	})
}

Promise.race

接收的是一个promise数组promises
返回一个promise,状态和值都取决于第一个改变状态的promise

Promise.race = function(promises) {
	return new Promise((resolve, reject)=>{
		for(let i = 0; i < promises.length; i++) {
			Promise.resolve(promises[i]).then(value => {
				resolve(value)
			}, reason => {
				reject(reason)
			}
		}
	})
}

Promise.resolve()

Promise.resolve = function(value) {
	return new Promise((resolve, reject) => {
		if(value instanceof Promise) {
			value.then(resolve, reject)
		} else {
			resolve(value)
		}
	}
}

Promise.reject()

Promise.reject = function(reason) {
	return new Promise((resolve, reject)=> {
		reject(reason)
	})
}

call

function call(Fn, obj, ...args) {
  if (obj === undefined || obj === null) {
    // 表示全局对象(ES11新增特性)
    obj = globalThis;
  }
  // 为 obj 添加临时的方法
  obj.temp = Fn;
  // 调用 temp 方法
  let result = obj.temp(...args);
  // 删除tempfangfa
  delete obj.temp;
  // 返回执行结果
  return result;
}

apply

function apply(Fn, obj, arr) {
  if (obj === undefined || obj === null) {
    obj = globalThis;
  }
  // 为obj添加临时方法
  obj.temp = Fn;
  // 执行方法
  let result = obj.temp(...arr);
  // 删除临时属性
  delete obj.temp;
  // 返回结果
  return result;
}

bind

function bind(Fn, obj, ...args) {
  // 返回一个新的函数
  return function (...args2) {
    // 执行 call 函数、
    return call(Fn, obj, ...args, ...args2);
  };
}

new

function newInstance(Fn, ...args) {
  // 1. 创建新对象
  // 创建空的object实例对象,作为Fn的实例对象
  const obj = {};
  // 修改新对象的原型对象
  // 将Fn的prototype(显式原型)属性赋值给obj的__proto__(隐式原型)属性
  obj.__proto__ = Fn.prototype;
  // 2. 修改函数内部this指向新对象,并执行
  //
  const result = Fn.call(obj, ...args);
  // 3. 返回新对象
  // return obj
  // 与new保持一直,如果构造函数有返回值,返回值是对象a就返回对象a,否则返回实例对象
  return result instanceof Object ? result : obj;
}

instanceof

function myInstanceOf(obj, Fn) {
  // 得到obj的隐式原型对象
  let protoObj = obj.__proto__;
  // 原型对象存在,就遍历原型链
  while (protoObj) {
    // 实例对象的隐式原型 等于 构造函数的显式原型 就返回true
    if (protoObj === Fn.prototype) {
      return true;
    }
    // 不相等就根据原型链一直往上找 直到最后为null
    protoObj = protoObj.__proto__;
  }
  return false;
}

2.2 自定义函数

数组去重

function unique1(arr) {
  const result = [];
  const obj = {};
  arr.forEach((item) => {
    if (!obj.hasOwnProperty(item)) {
      obj[item] = true;
      result.push(item);
    }
  });
  return result;
}
function unique(arr) {
  return [...new Set(arr)];
}
function objSort(obj){
    let newObj = {}
    //遍历对象,并将key进行排序
    Object.keys(obj).sort().map(key => {
        newObj[key] = obj[key]
    })
    //将排序好的数组转成字符串
    return JSON.stringify(newObj)
}

function unique(arr){
    let set = new Set();
    for(let i=0;i<arr.length;i++){
        let str = objSort(arr[i])
        set.add(str)
    }
    //将数组中的字符串转回对象
    arr = [...set].map(item => {
        return JSON.parse(item)
    })
    return arr
}

数组扁平化

function flatten(arr) {
  let result = [];
  arr.forEach((item) => {
    // 判断是不是数组
    if (Array.isArray(item)) {
      result = result.concat(flatten(item));
    } else {
      result = result.concat(item);
    }
  });
  return result;
}
function flatten1(arr) {
  let result = [...arr];
  // 判断result里有没有子数组
  while (result.some((item) => Array.isArray(item))) {
    result = [].concat(...result);
  }
  return result;
}

深拷贝

function deepClone1(target) {
  // 通过数据创建JSON格式的字符串
  let str = JSON.stringify(target);
  // 将 JSON 字符串创建为JS数据
  let data = JSON.parse(str);
  return data;
}
function deepClone(target) {
  if (typeof target === "object" && target !== null) {
    // 创建一个容器
    const result = Array.isArray(target) ? [] : {};
    // 遍历target
    for (let key in target) {
      // 检测该属性是否为对象本身的属性(不能拷贝原型对象上的属性)
      if (target.hasOwnProperty(key)) {
        result[key] = deepClone2(target[key]);
      }
    }
    return result;
  } else {
    return target;
  }
}
function deepClone3(target, map = new Map()) {
  if (typeof target === "object" && target !== null) {
    // 克隆数据之前进行判断,查看数据之前是否被克隆过
    let cache = map.get(target);
    if (cache) {
      return cache;
    }
    // 创建一个容器
    const result = Array.isArray(target) ? [] : {};
    // 将新的结果保存到容器中
    map.set(target, result);
    // 遍历target
    for (let key in target) {
      // 检测该属性是否为对象本身的属性(不能拷贝原型对象上的属性)
      if (target.hasOwnProperty(key)) {
        result[key] = deepClone3(target[key], map);
      }
    }
    return result;
  } else {
    return target;
  }
}

深比较

function isObject(obj) {
  return typeof obj === object && obj !== null;
}

// 深度比较
function isEqual(obj1, obj2) {
  if (!isObject(obj1) || !isObject(obj2)) {
    // 值类型,直接判断【一般不会传函数,不考虑函数】
    return obj1 === obj2;
  }
  if (obj1 === obj2) {
    return true;
  }
  // 两个都是对象或数组,而且不相等
  // 1. 先判断键的个数是否相等,不相等一定返回false
  const obj1Keys = Object.keys(obj1);
  const obj2Keys = Objext.keys(obj2);
  if (obj1Keys.length !== obj2Keys.length) {
    return false;
  }
  // 2. 以obj1为基准,和obj2依次递归比较
  for (let key in obj1) {
    // 递归比较
    const res = isEqual(obj1[key], obj2[key]);
    if (!res) {
      return false;
    }
  }
  // 3. 全相等
  return true;
}

节流

使用定时器版本

function throttle(callback, wait=100){
	let flag = true
	return function(){
		if(flag){
			flag = false
			setTimeout(()=>{
				flag = true
				callback(...arguments)
			}, wait)
		}
	}
}

不使用定时器版本

function throttle(callback, wait=100){
	let start = 0
	return function(){
		let now = Date.now()
		if(now - start >= wait) {
			callback(...arguments)
			start = now
		}
	}
}

防抖

连续触发事件,只有最后一次成功

function debounce(callback, wait=100) {
  // 定时器变量
  let timeId = undefined;
  // 返回一个函数
  return function () {
  	// 有定时器,就清空,取消定时器,阻止回调执行
    if (timeId !== undefined) {
      // 清空定时器
      clearTimeout(timeId);
    }
    
    // 启动定时器
    timeId = setTimeout(() => {
      // 执行回调
      callback(...arguments);
      // 执行完了重置id
      timeId = undefined;
    }, wait);
  };
}

寄生式组合继承

function Person(obj) {
    this.name = obj.name
    this.age = obj.age
}
Person.prototype.add = function(value){
    console.log(value)
}
var p1 = new Person({name:"番茄", age: 18})

function Person1(obj) {
    Person.call(this, obj)
    this.sex = obj.sex
}
// 这一步是继承

以上是关于面试前端面试常考手写题 - JavaScript - CSS的主要内容,如果未能解决你的问题,请参考以下文章

百度前端常考vue面试题(附答案)

2021前端面试之JavaScript手写题

2021前端面试之JavaScript手写题

2021前端面试之JavaScript手写题

JavaScript手撕前端面试题:手写new操作符 | 手写Object.freeze

前端面试题之手写代码篇