造轮子!
优秀的前端er一定要学会自己造轮子 ---- 柴可夫斯基
# 数据类型的判断
前置知识
(1) call() 允许为不同的对象分配和调用属于一个对象的函数/方法。
function typeOf(obj) {
// 这里的call就是调用this本身
// call与apply的区别在于call用逗号隔开,apply用数组隔开
let res = Object.prototype.toString.call(obj).split(' ')[1]
// 如果写 obj.toString的话,console.log()会得到[function: toString]
// 如果写 obj.toString()的话,console.log()会得到a中的值
res = res.substring(0,res.length - 1).toLowerCase()
// 这里有两个作用,第一个是把最后的“]”给弄掉
// 第二个作用是转化为小写字母
return res
}
阶段代码
// Object.prototype.toString.call(obj) -> [object ...(可能是array/object/date)]
function typeOf(obj) {
let res = Object.prototype.toString.call(obj)
res = res.substring(0,res.length-1).toLowerCase()
return res
}
console.log(typeOf([])) // 得到[object array]
console.log(typeOf({})) // 得到[object object]
console.log(typeOf(new Date)) // 得到[object date]
更简便的ANS写法
function typeOf(obj) {
// 用slice代替两次分割的写法
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}
反思
Function.prototype.toString和Object.prototype.toString的关系:
调用一个方法的时候是顺着原型链往上找,由于Function是Object的子类型,Function也有toString方法
向上找的时候就会被Function.toString拦住,但实际要类型的话要Object.toString才能得的到
# 数组去重
前置知识
(1)set的值总是唯一的
(2)filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
// ES6写法
var unique = arr => [...new Set(arr)]
// 两个部分,首先这里把arr放进去构造了一个Set,然后就不重合了
// 然后咱们把他展开到数组里了
function unique(arr) {
// 这里用indexOf方法标记位置,只有第一个出现的item会被留下,达到了去重的效果
var res = arr.filter(function(item,index,array) {
return arr.indexOf(item) === index
})
return res
}
# 数组扁平化
前置知识
(1) flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。「flat(idx)可以填入参数idx,idx为几表示去除几重嵌套」「同时,flat可以去除数组中的空元素」 (2) concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
[1,[2,[3]]].flat(2) // [1,2,3]
ES5 实现:递归
function flatten(arr){
var result = []
for(let i=0,len = arr.length;i<len;i++){
if(Array.isArray(arr[i])){
// 如果是数组就把你给消掉然后继续迭代
result = result.concat(flatten(arr[i]))
}
else{
// 不是数组是数就给你送进去
result.push(arr[i])
}
}
}
ES6 实现:
// 这个就没那么多花里胡哨的东西了,就是直接...展开运算符直接展开,何必递归呢
function flatten(arr) {
// some只要有一个是满足有数组的,那就直接为true,然后开始展开
while(arr.some(item => Array.isArray(item))){
// 语法: const arr3 = arr1.concat(arr2)
arr = [].concat(...arr)
}
return arr
}
# 深浅拷贝
浅拷贝:只考虑对象类型
前置知识:
(1) hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
function shallowCopy(obj) {
// 因为只考虑对象类型,所以如果不是对象类型,就请离开(
if(typeof obj !== 'object' ) return
// 创建一个新的对象
let newObj = obj instanceof Array ? [] : {}
for(key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key]
}
}
return newObj
}
简单版本深拷贝:只考虑普通对象属性,不考虑内置的对象和函数。
function deepClone(obj) {
// 因为只考虑对象类型,所以如果不是对象类型,就请离开(
if(typeof obj !== 'object' ) return
// 创建一个新的对象
let newObj = obj instanceof Array ? [] : {}
for(key in obj){
// hasOwnProperty是只要在这个实例里面的属性,而不是在这个原型里的属性
if(obj.hasOwnProperty(key)){
// 递归处理,完全赋到具体的值,而不是一个对象直接复制过去
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj
}
# 手写apply && bind && call
手写apply
// apply是一个数组传参「args」
Function.prototype.myApply = function (context,args){
// 这里context其实就相当于this
// 处理容错
context = (typeof context === 'object' ? context : window)
args = args ? args : []
// 给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
context[key] = this
// 通过隐式绑定的方式调用函数
const result = context[key](...args)
// 删除添加的属性
delete context[key]
return result
}
手写call
Function.prototype.myCall = function(context,...args){
context = ( typeof context === 'object' ? context : window )
args = args ? args : []
const key = Symbol()
context[key] = this
let result = context[key](...args)
delete context
return result
}
手写bind
Function.prototype.myBind = function(objThis,...params){
const thisFn = this;//存储源函数以及上方的params(函数参数)
// 对返回的函数参数 secondParams 二次传参
let fToBind = function(...secondParams) {
const isNew = this instanceof fToBind //this是否是fToBind的实例,也就是返回的fToBind是否通过new调用
const context = isNew ? this : Object(objThis)// new调用就绑定到this上,否则就绑定到传入的objThis上
return thisFn.call(context,...params,...secondParams);//用call调用源函数绑定this的指向并传递参数,返回执行结果
};
if(thisFn.prototype) {
// 复制源函数的prototype给fToBind 一般情况下没有prototype,比如箭头函数。
fToBind.prototype = Object.create(thisFn.prototype);
}
return fToBind
}