前端面试之手写代码
栏目: JavaScript · 发布时间: 5年前
内容简介:(一维)数组去重最原始的方法就是使用双层循环,分别循环原始数组和新建数组;或者我们可以使用再简化下
数组去重
(一维)数组去重最原始的方法就是使用双层循环,分别循环原始数组和新建数组;或者我们可以使用 indexOf
来简化内层的循环;或者可以将原始数组 排序 完再来去重,这样会减少一个循环,只需要比较前后两个数即可;当然我们可以使用 ES5
, ES6
的方法来简化去重的写法,比如我们可以使用 filter
来简化内层循环,或者使用 Set
、 Map
、扩展运算符这些用起来更简单的方法,但是效率上应该不会比原始方法好。二维数组的去重可以在上面方法的基础上再判断元素是不是数组,如果是的话,就进行递归处理。
双层循环
var array = [1, 1, '1', '1']; function unique(array) { var res = []; for (var i = 0, arrayLen = array.length; i < arrayLen; i++) { for (var j = 0, resLen = res.length; j < resLen; j++ ) { if (array[i] === res[j]) { break; } } if (j === resLen) { res.push(array[i]) } } return res; } console.log(unique(array)); // [1, "1"] 复制代码
利用indexOf
var array = [1, 1, '1']; function unique(array) { var res = []; for (var i = 0, len = array.length; i < len; i++) { var current = array[i]; if (res.indexOf(current) === -1) { res.push(current) } } return res; } console.log(unique(array)); 复制代码
排序后去重
var array = [1, 1, '1']; function unique(array) { var res = []; var sortedArray = array.concat().sort(); var seen; for (var i = 0, len = sortedArray.length; i < len; i++) { // 如果是第一个元素或者相邻的元素不相同 if (!i || seen !== sortedArray[i]) { res.push(sortedArray[i]) } seen = sortedArray[i]; } return res; } console.log(unique(array)); 复制代码
filter
filter
可以用来简化外层循环
使用indexOf:
var array = [1, 2, 1, 1, '1']; function unique(array) { var res = array.filter(function(item, index, array){ return array.indexOf(item) === index; }) return res; } console.log(unique(array)); 复制代码
排序去重:
var array = [1, 2, 1, 1, '1']; function unique(array) { return array.concat().sort().filter(function(item, index, array){ return !index || item !== array[index - 1] }) } console.log(unique(array)); 复制代码
ES6方法
Set:
var array = [1, 2, 1, 1, '1']; function unique(array) { return Array.from(new Set(array)); } console.log(unique(array)); // [1, 2, "1"] 复制代码
再简化下
function unique(array) { return [...new Set(array)]; } //或者 var unique = (a) => [...new Set(a)] 复制代码
Map:
function unique (arr) { const seen = new Map() return arr.filter((a) => !seen.has(a) && seen.set(a, 1)) } 复制代码
类型判断
类型判断需要注意以下几点
-
typeof
对六个基本数据类型Undefined
、Null
、Boolean
、Number
、String
、Object
(大写)返回的结果是undefined
、object
、boolean
、number
、string
、object
(小写),可以看到Null
和Object
类型都返回了object
字符串;typeof
却能检测出函数类型; 综上,typeof
能检测出六种类型,但是不能检测出null
类型和Object
下细分的类型,如Array
,Function
,Date
,RegExp
,Error
等 。 -
Object.prototype.toString
的作用非常强大,它能检测出基本数据类型以及Object
下的细分类型,甚至像Math
,JSON
,arguments
它都能检测出它们的具体类型,它返回结果形式例如[object Number]
(注意最后的数据类型是大写). 所以,Object.prototype.toString
基本上能检测出所有的类型了,只不过有时需要考虑到兼容性低版本浏览器的问题。
通用API
// 该类型判断函数可以判断六种基本数据类型以及Boolean Number String Function Array Date RegExp Object Error, // 其他类型因为遇到类型判断的情况较少所以都会返回object,不在进行详细的判断 // 比如ES6新增的Symbol,Map,Set等类型 var classtype = {}; "Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item) { classtype["[object " + item + "]"] = item.toLowerCase(); }) function type(obj) { // 解决IE6中null和undefined会被Object.prototype.toString识别成[object Object] if (obj == null) { return obj + ""; } //如果是typeof后类型为object下的细分类型(Array,Function,Date,RegExp,Error)或者是Object类型,则要利用Object.prototype.toString //由于ES6新增的Symbol,Map,Set等类型不在classtype列表中,所以使用type函数,返回的结果会是object return typeof obj === "object" || typeof obj === "function" ? classtype[Object.prototype.toString.call(obj)] || "object" : typeof obj; } 复制代码
判断空对象
判断是否有属性, for
循环一旦执行,就说明有属性,此时返回 false
function isEmptyObject( obj ) { var name; for ( name in obj ) { return false; } return true; } console.log(isEmptyObject({})); // true console.log(isEmptyObject([])); // true console.log(isEmptyObject(null)); // true console.log(isEmptyObject(undefined)); // true console.log(isEmptyObject(1)); // true console.log(isEmptyObject('')); // true console.log(isEmptyObject(true)); // true 复制代码
我们可以看出 isEmptyObject
实际上判断的并不仅仅是空对象。但是既然 jQuery
是这样写,可能是因为考虑到实际开发中 isEmptyObject
用来判断 {} 和 {a: 1} 是足够的吧。如果真的是只判断 {},完全可以结合上篇写的 type
函数筛选掉不适合的情况。
判断Window对象
Window
对象有一个 window
属性指向自身,可以利用这个特性来判断是否是 Window
对象
function isWindow( obj ) { return obj != null && obj === obj.window; } 复制代码
判断数组
isArray
是数组类型内置的数据类型判断函数,但是会有兼容性问题,一个 polyfill
如下
isArray = Array.isArray || function(array){ return Object.prototype.toString.call(array) === '[object Array]'; } 复制代码
判断类数组
jquery
实现的 isArrayLike
,数组和类数组都会返回 true
。所如果 isArrayLike
返回 true
,至少要满足三个条件之一:
-
是数组
-
长度为 0 比如下面情况,如果我们去掉length === 0 这个判断,就会打印
false
,然而我们都知道arguments
是一个类数组对象,这里是应该返回true
的function a(){ console.log(isArrayLike(arguments)) } a(); 复制代码
-
lengths
属性是大于 0 的数字类型,并且obj[length - 1]
必须存在(考虑到arr = [,,3]的情况)
function isArrayLike(obj) { // obj 必须有 length属性 var length = !!obj && "length" in obj && obj.length; var typeRes = type(obj); // 排除掉函数和 Window 对象 if (typeRes === "function" || isWindow(obj)) { return false; } return typeRes === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj; } 复制代码
判断NaN
判断一个数是不是 NaN
不能单纯地使用 === 这样来判断, 因为 NaN
不与任何数相等, 包括自身,注意在 ES6
的 isNaN
中只有值为数字类型使用 NaN
才会返回 true
isNaN: function(value){ return isNumber(value) && isNaN(value); } 复制代码
判断DOM元素
利用 DOM
对象特有的 nodeType
属性(
isElement: function(obj){ return !!(obj && obj.nodeType === 1); // 两次感叹号将值转化为布尔值 } 复制代码
判断arguments对象
低版本的浏览器中 argument
对象通过 Object.prototype.toString
判断后返回的是 [object Object]
,所以需要兼容
isArguments: function(obj){ return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee')); } 复制代码
深浅拷贝
如果是数组,实现浅拷贝,比可以 slice
, concat``返回一个新数组的特性来实现;实现深拷贝,可以利用JSON.parse
和 JSON.stringify
来实现,但是有一个问题,不能拷贝函数(此时拷贝后返回的数组为null)。上面的方法都属于技巧,下面考虑怎么实现一个对象或者数组的深浅拷贝
浅拷贝
思路很简单,遍历对象,然后把属性和属性值都放在一个新的对象就OK了
var shallowCopy = function(obj) { // 只拷贝对象 if (typeof obj !== 'object') return; // 根据obj的类型判断是新建一个数组还是对象 var newObj = obj instanceof Array ? [] : {}; // 遍历obj,并且判断是obj的属性才拷贝 for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; } 复制代码
深拷贝
思路也很简单,就是在拷贝的时候判断一下属性值的类型,如果是对象,就递归调用深浅拷贝函数就ok了
var deepCopy = function(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' ? deepCopy(obj[key]) : obj[key]; } } return newObj; } 复制代码
扁平化
递归
循环数组元素,如果还是一个数组,就递归调用该方法
// 方法 1 var arr = [1, [2, [3, 4]]]; 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; } console.log(flatten(arr)) 复制代码
toString()
如果数组的元素都是数字,可以使用该方法
// 方法2 var arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.toString().split(',').map(function(item){ return +item // +会使字符串发生类型转换 }) } console.log(flatten(arr)) 复制代码
reduce()
// 方法3 var arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.reduce(function(prev, next){ return prev.concat(Array.isArray(next) ? flatten(next) : next) }, []) } console.log(flatten(arr)) 复制代码
...
// 扁平化一维数组 var arr = [1, [2, [3, 4]]]; console.log([].concat(...arr)); // [1, 2, [3, 4]] // 可以扁平化多维数组 var arr = [1, [2, [3, 4]]]; function flatten(arr) { while (arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr; } console.log(flatten(arr)) 复制代码
防抖与节流
防抖
function debounce(fn, wait) { var timeout = null; return function() { if(timeout !== null) { clearTimeout(timeout); } timeout = setTimeout(fn, wait); } } // 处理函数 function handle() { console.log(Math.random()); } // 滚动事件 window.addEventListener('scroll', debounce(handle, 1000)); 复制代码
节流
利用时间戳实现
var throttle = function(func, delay) { var prev = Date.now(); return function() { var context = this; var args = arguments; var now = Date.now(); if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000)); 复制代码
利用定时器实现
var throttle = function(func, delay) { var timer = null; return function() { var context = this; var args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, delay); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000)); 复制代码
模拟new
-
new
产生的实例可以访问Constructor
里的属性,也可以访问到Constructor.prototype
中的属性,前者可以通过apply
来实现,后者可以通过将实例的proto
属性指向构造函数的prototype
来实现 - 我们还需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么
function New(){ var obj=new Object(); //取出第一个参数,就是我们要传入的构造函数;此外因为shift会修改原数组,所以arguments会被去除第一个参数 Constructor=[].shift.call(arguments); //将obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性 obj._proto_=Constructor.prototype; //使用apply改变构造函数this的指向到新建的对象,这样obj就可以访问到构造函数中的属性 var ret=Constructor.apply(obj,arguments); //要返回obj return typeof ret === 'object' ? ret:obj; } 复制代码
function Otaku(name,age){ this.name=name; this.age=age; this.habit='Games' } Otaku.prototype.sayYourName=function(){ console.log("I am" + this.name); } var person=objectFactory(Otaku,'Kevin','18') console.log(person.name)//Kevin console.log(person.habit)//Games console.log(person.strength)//60 复制代码
模拟Call
-
call()
方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法 - 模拟的步骤是:将函数设为对象的属性—>执行该函数—>删除该函数
-
this
参数可以传null
,当为null
的时候,视为指向window
- 函数是可以有返回值的
简单版
var foo = { value: 1, bar: function() { console.log(this.value) } } foo.bar() // 1 复制代码
完善版
Function.prototype.call2 = function(context) { var context=context||window context.fn = this; let args = [...arguments].slice(1); let result = context.fn(...args); delete context.fn; return result; } let foo = { value: 1 } function bar(name, age) { console.log(name) console.log(age) console.log(this.value); } //表示bar函数的执行环境是foo,即bar函数里面的this代表foo,this.value相当于foo.value,然后给bar函数传递两个参数 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(context){ var self=thisl var args=Array.prototype.slice.call(arguments,1); var fNOP=function(){}; var fBound=function(){ var bindArgs=Array.prototype.slice.call(arguments); return self.apply(this instanceof fNOP ? this : context, args.concat(bindAt)) } } 复制代码
模拟instanceof
function instanceOf(left,right) { let proto = left.__proto__; let prototype = right.prototype while(true) { if(proto === null) return false if(proto === prototype) return true proto = proto.__proto__; } } 复制代码
模拟JSON.stringify
JSON.stringify(value[, replacer [, space]])
-
Boolean | Number| String
类型会自动转换成对应的原始值。 -
undefined
、任意函数以及symbol
,会被忽略(出现在非数组对象的属性值中时),或者被转换成null
(出现在数组中时)。 -
不可枚举的属性会被忽略
-
如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
function jsonStringify(obj) { let type = typeof obj; if (type !== "object") { if (/string|undefined|function/.test(type)) { obj = '"' + obj + '"'; } return String(obj); } else { let json = [] let arr = Array.isArray(obj) for (let k in obj) { let v = obj[k]; let type = typeof v; if (/string|undefined|function/.test(type)) { v = '"' + v + '"'; } else if (type === "object") { v = jsonStringify(v); } json.push((arr ? "" : '"' + k + '":') + String(v)); } return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}") } } jsonStringify({x : 5}) // "{"x":5}" jsonStringify([1, "false", false]) // "[1,"false",false]" jsonStringify({b: undefined}) // "{"b":"undefined"}" 复制代码
参考资料:
以上所述就是小编给大家介绍的《前端面试之手写代码》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机程序设计艺术 第2卷 半数值算法(第3版)(英文影印版)
(美)Donald E.Knuth / 清华大学出版社 / 2002-09-01 / 83.0
计算机程序设计艺术:英文版(第2卷 半数值算法),ISBN:9787302058151,作者:(美)Donald E. Knuth著一起来看看 《计算机程序设计艺术 第2卷 半数值算法(第3版)(英文影印版)》 这本书的介绍吧!