这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
前言
本辑主要介绍面试中常见的手写代码题,给出思路并附上代码,以供享用。对于不便于理解的,可以多查看资料加强理解,加深记忆。没有什么代码是写个百八十次不能熟练的。
主体
new
操作符
new
操作符做了这些事:
- 它创建了一个全新的对象。
- 把这个对象链接到原型对象上,它会被执行
[[Prototype]]
(也就是__proto__
)链接。 - 它使
this
指向新创建的对象。通过new
创建的每个对象将最终被[[Prototype]]
链接到这个函数的prototype
对象上。 - 如果这个函数不返回任何东西,那么
new
表达式中的函数调用将返回该对象引用,默认return this
function _New(fn,...arg){
//创建一个空对象
//设定原型 将其隐式原型指向构造函数原型,设置为函数的 prototype 对象。
let obj = {
__proto__ : fn.prototype
}
//让函数的 this 指向这个对象并执行构造函数代码
fn.apply(obj,arg);
//返回这个对象
return obj;
}
function Person (name,age){
this.name = name;
this.age = age;
}
let f = _New (Person,'huang','21')
console.log(f);
实现一个 instanceOf
function instanceOf(left,right) {//left 表示左表达式,right 表示右表达式
let proto = left.__proto__;
let prototype = right.prototype
while(true) {
if(proto === null) return false
// 这里重点:当 prototype 严格等于 proto 时,返回 true\
if(proto === prototype) return true
proto = proto.__proto__;
}
}
// 开始测试
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // true
console.log(instance_of(d, father)) // true
数组去重
//去掉数组中重复性的数据
function trimSameItem(arr){
var newArr = [];
for (var i = 0; i < arr.length; i++) {
//方法一
// if(newArr.indexOf(arr[i]) === -1){
// newArr.push(arr[i]);
// }
//方法二
if(!newArr.includes(arr[i])){
newArr.push(arr[i]);
}
}
return newArr;
}
var arr = [8,11,20,5,20,8,0,2,4,0,8,19];
console.log(trimSameItem(arr));
// 方法 三
function unique(arr) {
var res = arr.filter(function(item, index, array) {
return array.indexOf(item) === index
})
return res
}
// 方法 四
/*
1.创建一个新数组,把原数组中的第一个元素插入到新数组中
2.遍历原数组中的每一个元素分别和新数组中的每一个元素进行比较
*/
//原数组
var arr = [8,11,20,5,20,8,0,2,4,0,8];
//新数组
var t = [];//var t=[8,11];
// 初始的 t 中没有元素则直接将 arr 的第一个元素插入到 t 中
t[0] = arr[0];
let arrLen = arr.length;
let tLen = t.length;
//遍历 arr 中的每个元素
for(var i=o;i<arrLen;i++){
//遍历 t 中的每个元素
for(var k = 0;k<tLen;k++){
//当原数组中的值和新数组中的值相同的时候,就没有必要再继续比较了,跳出内循环
if(t[k] == arr[i]){
break;
}
//拿原数组中的某个元素比较到新数组中的最后一个元素还没有重复
// 确保最后的元素都比较完之后再行插入
if(k == tLen - 1){
//将数据插入新数组
t.push(arr[i]);
}
}
}
console.log(t);
// 方法六 ES6 方案
[...new Set(arr)];
// or
Array.from(new Set(arr));
数组扁平化
数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。使用 Array.prototype.flat 可以直接将多层数组拍平成一层:
[1, [2, [3]]].flat(2) // [1, 2, 3]
现在就是要实现 flat 这种效果。
// ES5 实现:递归
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
return result;
}
// ES6 实现
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
反转数组
//反转数组
function turnArray(arr) {
for (var i = 0; i < arr.length/2; i++) {
var temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}
return arr;
}
var arr = [1,23,7,3,4,5,6,7,8,9];
console.log(turnArray(arr));
转换字符串为驼峰命名
//转换字符串为驼峰命名
function toTuoFeng(params) {
if(!params){
console.log('参数不存在');
return;
}
var arr = foo.split('-');
// console.log(arr[1].charAt(0).toUpperCase()+arr[1].substr(1,arr[1].length-1));
for (var i = 1; i < arr.length; i++) {
arr[i] = arr[i].charAt(0).toUpperCase()+arr[i].substr(1,arr[i].length-1);
}
return arr.join('');
}
var foo = 'get-element-by-id';
var zhuan = toTuoFeng(foo);
console.log(zhuan);
冒泡排序
//冒泡排序
var arr = [5,4,9,23,7,2,1];
//论数 arr.length-1
for (var i = 0; i < arr.length-1; i++) {
console.log(i);
for (var j = 0; j < arr.length-1-i; j++) {
//j>j+1二者对调位置
if(arr[j]>arr[j+1]){
var temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
//倒序
// if(arr[j]<arr[j+1]){
// temp = arr[j+1];
// arr[j+1] = arr[j];
// arr[j] = temp;
// }
}
}
console.log(arr);
实现 Call
call
语法:
fun.call(thisArg, arg1, arg2, ...)
,调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。call
核心:
- 将函数设为对象的属性
- 执行&删除这个函数
- 指定
this
到函数并传入给定参数执行函数 - 如果不传入参数,默认指向为 window
- this 可能传入 null;
- 传入不固定个数的参数;
- 函数可能有返回值;
Function.prototype.call2 = function(content = window) {
content.fn = this;
let args = [...arguments].slice(1);
let result = content.fn(...args);
delete content.fn;
return result;
}
let foo = {
value: 1
}
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'black', '18') // black 18 1
apply
的模拟实现
apply()
的实现和call()
类似,只是参数形式不同。
Function.prototype.apply2 = function(context = window) {
context.fn = this
let result;
// 判断是否有第二个参数
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind()
的模拟实现
Function.prototype.bind2 = function(content) {
if(typeof this != "function") {
throw Error("not a function")
}
// 若没问参数类型则从这开始写
let fn = this;
let args = [...arguments].slice(1);
let resFn = function() {
return fn.apply(this instanceof resFn ? this : content,args.concat(...arguments) )
}
function tmp() {}
tmp.prototype = this.prototype;
resFn.prototype = new tmp();
return resFn;
}
实现一个JS函数柯里化
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
// ES6 版本
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
手写一个Promise
我们来过一遍Promise/A+
规范:
- 三种状态
pending| fulfilled(resolved) | rejected
- 当处于
pending
状态的时候,可以转移到fulfilled(resolved)
或者rejected
状态 - 当处于
fulfilled(resolved)
状态或者rejected
状态的时候,就不可变。
- 必须有一个
then
异步执行方法,then
接受两个参数且必须返回一个promise:
var promise = new Promise((resolve,reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
节流(Throttling
)实现
触发高频事件,且 N 秒内只执行一次。
function throttle(fn, wait) {
let prev = new Date();
return function() {
const args = arguments;
const now = new Date();
if (now - prev > wait) {
fn.apply(this, args);
prev = new Date();
}
}
防抖函数 有问题
触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会重新计时。
// 防抖动函数
function debounce(func, wait=50, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
浅拷贝
var obj = {
a:'hellow',
b:{
a:'world',
b:100
},
c:[1,2,3]
}
//1. 遍历赋值
function simpleClone(objNew){
var obj = {};
for(var i in objNew){
obj[i] = objNew[i];
}
return obj;
}
//2. Object.create()
let objNew = Object.create(obj)
// 原对象被赋值到 objNew 的 __proto__ 上去了
//3. JSON.parse() 和 JSON.stringify()
let objNew = JSON.parse(JSON.stringify(obj));
深拷贝 有问题
深拷贝与浅拷贝,简单点来说: 就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝; 如果B没变,那就是深拷贝。
funcition deepClone(startObj, endObj){
var obj = endObj || {};
for(var i in startObj){
if(typeof(startObj[i]) === 'object'){ // [] {}
obj[i] = startObj[i].constructor === Array ? [] : {}
deepClone(startObj, obj[i]);
} else {
obj[i] = startObj[i];
}
}
return obj;
}
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;
}
function deepCopy(obj){//有问题
//判断是否是简单数据类型,
if(typeof obj == "object"){
//复杂数据类型
var result = obj.constructor == Array ? [] : {};
for(let i in obj){
result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
}
}else {
//简单数据类型 直接 == 赋值
var result = obj;
}
return result;
}
解析 URL 参数为对象
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
字符串模版
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}
Ajax
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
总结
即使看明白看懂思路也不见得就能顺溜的写出来,有时候还是要练一练的,本专辑持续更新优化...