实现一个函数判断数据类型
function getType(obj) { if (obj === null) return String(obj); return typeof obj === 'object' ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase() : typeof obj;}
// 调用getType(null); // -> nullgetType(undefined); // -> undefinedgetType({}); // -> objectgetType([]); // -> arraygetType(123); // -> numbergetType(true); // -> booleangetType('123'); // -> stringgetType(/123/); // -> regexpgetType(new Date()); // -> date
复制代码
验证是否是身份证
function isCardNo(number) { var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; return regx.test(number);}
复制代码
实现一个 call
call 做了什么:
将函数设为对象的属性
执行 &删除这个函数
指定 this 到函数并传入给定参数执行函数
如果不传入参数,默认指向为 window
// 模拟 call bar.mycall(null);//实现一个call方法:Function.prototype.myCall = function(context) { //此处没有考虑context非object情况 context.fn = this; let args = []; for (let i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } context.fn(...args); let result = context.fn(...args); delete context.fn; return result;};
复制代码
实现防抖函数(debounce)
防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
那么与节流函数的区别直接看这个动画实现即可。
手写简化版:
// 防抖函数const debounce = (fn, delay) => { let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); };};
复制代码
适用场景:
生存环境请用 lodash.debounce
实现 AJAX 请求
AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
创建 AJAX 请求的步骤:
创建一个 XMLHttpRequest 对象。
在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发 onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。
const SERVER_URL = "/server";let xhr = new XMLHttpRequest();// 创建 Http 请求xhr.open("GET", SERVER_URL, true);// 设置状态监听函数xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 当请求成功时 if (this.status === 200) { handle(this.response); } else { console.error(this.statusText); }};// 设置请求失败时的监听函数xhr.onerror = function() { console.error(this.statusText);};// 设置请求头信息xhr.responseType = "json";xhr.setRequestHeader("Accept", "application/json");// 发送 Http 请求xhr.send(null);
复制代码
Promise.all
Promise.all是支持链式调用的,本质上就是返回了一个 Promise 实例,通过resolve和reject来改变实例状态。
Promise.myAll = function(promiseArr) { return new Promise((resolve, reject) => { const ans = []; let index = 0; for (let i = 0; i < promiseArr.length; i++) { promiseArr[i] .then(res => { ans[i] = res; index++; if (index === promiseArr.length) { resolve(ans); } }) .catch(err => reject(err)); } })}
复制代码
参考 前端进阶面试题详细解答
Array.prototype.filter()
Array.prototype.filter = function(callback, thisArg) { if (this == undefined) { throw new TypeError('this is null or not undefined'); } if (typeof callback !== 'function') { throw new TypeError(callback + 'is not a function'); } const res = []; // 让O成为回调函数的对象传递(强制转换对象) const O = Object(this); // >>>0 保证len为number,且为正整数 const len = O.length >>> 0; for (let i = 0; i < len; i++) { // 检查i是否在O的属性(会检查原型链) if (i in O) { // 回调函数调用传参 if (callback.call(thisArg, O[i], i, O)) { res.push(O[i]); } } } return res;}
复制代码
实现 bind
实现 bind 要做什么
// mdn的实现if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); }
var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用 return fToBind.apply(this instanceof fBound ? this : oThis, // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的 aArgs.concat(Array.prototype.slice.call(arguments))); };
// 维护原型关系 if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } // 下行的代码使fBound.prototype是fNOP的实例,因此 // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例 fBound.prototype = new fNOP();
return fBound; };}
复制代码
手写 Promise
const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";
function MyPromise(fn) { // 保存初始化状态 var self = this;
// 初始化状态 this.state = PENDING;
// 用于保存 resolve 或者 rejected 传入的值 this.value = null;
// 用于保存 resolve 的回调函数 this.resolvedCallbacks = [];
// 用于保存 reject 的回调函数 this.rejectedCallbacks = [];
// 状态转变为 resolved 方法 function resolve(value) { // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变 if (value instanceof MyPromise) { return value.then(resolve, reject); }
// 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变, if (self.state === PENDING) { // 修改状态 self.state = RESOLVED;
// 设置传入的值 self.value = value;
// 执行回调函数 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); }
// 状态转变为 rejected 方法 function reject(value) { // 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变 if (self.state === PENDING) { // 修改状态 self.state = REJECTED;
// 设置传入的值 self.value = value;
// 执行回调函数 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); }
// 将两个方法传入函数执行 try { fn(resolve, reject); } catch (e) { // 遇到错误时,捕获错误,执行 reject 函数 reject(e); }}
MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; };
onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; };
// 如果是等待状态,则将函数加入对应列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); }
// 如果状态已经凝固,则直接执行对应状态的函数
if (this.state === RESOLVED) { onResolved(this.value); }
if (this.state === REJECTED) { onRejected(this.value); }};
复制代码
实现非负大整数相加
JavaScript 对数值有范围的限制,限制如下:
Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991
复制代码
如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) { let res = ''; let temp = 0;
a = a.split(''); b = b.split('');
while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9 } return res.replace(/^0+/, '');}
复制代码
其主要的思路如下:
首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
初始化 res,temp 来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
将两个数组的对应的位进行相加,两个数相加的结果可能大于 10,所以可能要仅为,对 10 进行取余操作,将结果保存在当前位
判断当前位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会自动隐式转化为 1,以便于下一次相加
重复上述操作,直至计算结束
手写 instanceof 方法
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
实现步骤:
首先获取类型的原型
然后获得对象的原型
然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
具体实现:
function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left), // 获取对象的原型 prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto); }}
复制代码
实现 apply 方法
apply 原理与 call 很相似,不多赘述
// 模拟 applyFunction.prototype.myapply = function(context, arr) { var context = Object(context) || window; context.fn = this;
var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); }
delete context.fn; return result;};
复制代码
手写 apply 函数
apply 函数的实现步骤:
判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
判断传入上下文对象是否存在,如果不存在,则设置为 window 。
将函数作为上下文对象的一个属性。
判断参数值是否传入
使用上下文对象来调用这个方法,并保存返回结果。
删除刚才新增的属性
返回结果
// apply 函数实现Function.prototype.myApply = function(context) { // 判断调用对象是否为函数 if (typeof this !== "function") { throw new TypeError("Error"); } let result = null; // 判断 context 是否存在,如果未传入则为 window context = context || window; // 将函数设为对象的方法 context.fn = this; // 调用方法 if (arguments[1]) { result = context.fn(...arguments[1]); } else { result = context.fn(); } // 将属性删除 delete context.fn; return result;};
复制代码
JSONP
script 标签不遵循同源协议,可以用来进行跨域请求,优点就是兼容性好但仅限于 GET 请求
const jsonp = ({ url, params, callbackName }) => { const generateUrl = () => { let dataSrc = ''; for (let key in params) { if (Object.prototype.hasOwnProperty.call(params, key)) { dataSrc += `${key}=${params[key]}&`; } } dataSrc += `callback=${callbackName}`; return `${url}?${dataSrc}`; } return new Promise((resolve, reject) => { const scriptEle = document.createElement('script'); scriptEle.src = generateUrl(); document.body.appendChild(scriptEle); window[callbackName] = data => { resolve(data); document.removeChild(scriptEle); } })}
复制代码
实现数组的 flat 方法
function _flat(arr, depth) { if(!Array.isArray(arr) || depth <= 0) { return arr; } return arr.reduce((prev, cur) => { if (Array.isArray(cur)) { return prev.concat(_flat(cur, depth - 1)) } else { return prev.concat(cur); } }, []);}
复制代码
实现浅拷贝
浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。
(1)Object.assign()
Object.assign()是 ES6 中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:Object.assign(target, source_1, ···),该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。
注意:
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
因为null 和 undefined 不能转化为对象,所以第一个参数不能为null或 undefined,会报错。
let target = {a: 1};let object2 = {b: 2};let object3 = {c: 3};Object.assign(target,object2,object3); console.log(target); // {a: 1, b: 2, c: 3}
复制代码
(2)扩展运算符
使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法:let cloneObj = { ...obj };
let obj1 = {a:1,b:{c:1}}let obj2 = {...obj1};obj1.a = 2;console.log(obj1); //{a:2,b:{c:1}}console.log(obj2); //{a:1,b:{c:1}}obj1.b.c = 2;console.log(obj1); //{a:2,b:{c:2}}console.log(obj2); //{a:1,b:{c:2}}
复制代码
(3)数组方法实现数组浅拷贝
1)Array.prototype.slice
slice()方法是 JavaScript 数组的一个方法,这个方法可以从已有数组中返回选定的元素:用法:array.slice(start, end),该方法不会改变原始数组。
该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。
let arr = [1,2,3,4];console.log(arr.slice()); // [1,2,3,4]console.log(arr.slice() === arr); //false
复制代码
2)Array.prototype.concat
let arr = [1,2,3,4];console.log(arr.concat()); // [1,2,3,4]console.log(arr.concat() === arr); //false
复制代码
(4)手写实现浅拷贝
// 浅拷贝的实现;
function shallowCopy(object) { // 只拷贝对象 if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象 let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } }
return newObject;}// 浅拷贝的实现;
function shallowCopy(object) { // 只拷贝对象 if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象 let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } }
return newObject;}// 浅拷贝的实现;function shallowCopy(object) { // 只拷贝对象 if (!object || typeof object !== "object") return; // 根据 object 的类型判断是新建一个数组还是对象 let newObject = Array.isArray(object) ? [] : {}; // 遍历 object,并且判断是 object 的属性才拷贝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } } return newObject;}
复制代码
实现数组的 map 方法
Array.prototype._map = function(fn) { if (typeof fn !== "function") { throw Error('参数必须是一个函数'); } const res = []; for (let i = 0, len = this.length; i < len; i++) { res.push(fn(this[i])); } return res;}
复制代码
字符串解析问题
var a = { b: 123, c: '456', e: '789',}var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;// => 'a123aa456aa {a.d}aaaa'
复制代码
实现函数使得将 str 字符串中的{}内的变量替换,如果属性不存在保持原样(比如{a.d})
类似于模版字符串,但有一点出入,实际上原理大差不差
const fn1 = (str, obj) => { let res = ''; // 标志位,标志前面是否有{ let flag = false; let start; for (let i = 0; i < str.length; i++) { if (str[i] === '{') { flag = true; start = i + 1; continue; } if (!flag) res += str[i]; else { if (str[i] === '}') { flag = false; res += match(str.slice(start, i), obj); } } } return res;}// 对象匹配操作const match = (str, obj) => { const keys = str.split('.').slice(1); let index = 0; let o = obj; while (index < keys.length) { const key = keys[index]; if (!o[key]) { return `{${str}}`; } else { o = o[key]; } index++; } return o;}
复制代码
验证是否是邮箱
function isEmail(email) { var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/; return regx.test(email);}
复制代码
实现数组元素求和
arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]let sum = arr.reduce( (total,i) => total += i,0);console.log(sum);
复制代码
arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0);console.log(arr);
复制代码
递归实现:
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) { if (arr.length == 1) return arr[0] return arr[0] + add(arr.slice(1)) }console.log(add(arr)) // 21
复制代码
评论