数据类型判断
typeof 可以正确识别:Undefined、Boolean、Number、String、Symbol、Function 等类型的数据,但是对于其他的都会认为是 object,比如 Null、Date 等,所以通过 typeof 来判断数据类型会不准确。但是可以使用 Object.prototype.toString 实现。
function typeOf(obj) {
let temp = Object.prototype.toString.call(obj).split(' ')[1]
return temp.slice(0, temp.length-1).toLowerCase()
}
console.log(typeOf([])) //array
console.log(typeOf({})) //object
console.log(typeOf(new Date)) //date
手写 instanceof 方法
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);
}
}
es6手写promise
//初始结构 this指向 then函数 执行异常 异步(promise.then中的异步和promise加了setTimeout的异步以及promise本身resolve、reject的异步) 回调保存 链式
class Commitment {
static PENDING = '待定'; FULFILLED = '成功'; REJECT = '拒绝'
constructor (func) {
//初始状态为待定状态
this.status = Commitment.PENDING
//调用resolve和reject传过来的值
this.result = null
this.resolveCallbacks = []
this.rejectCallbacks = []
//promise的两个参数 (因为在外部调用,所以将构造函数中的this指向它)
//处理错误
try{
func(this.resolve.bind(this), this.reject.bind(this))
} catch (error) {
//把错误传递给reject
this.reject(error)
}
}
//定义这两个函数
resolve (result) {
setTimeout(()=>{
if(this.status == Commitment.PENDING) {
this.status = Commitment.FULFILLED
//将传过来的参数赋值给this.result
this.result = result
//待定状态的处理
this.resolveCallbacks.forEach((callback)=>{
callback(result)
})
}
})
}
reject (result) {
setTimeout(()=> {
if(this.status == Commitment.PENDING) {
this.status = Commitment.REJECT
this.result = result
//待定状态的处理
this.rejectCallbacks.forEach((callback)=>{
callback(result)
}
})
}
//链式功能
return new Commitment((resolve, reject)=>{
//then方法 (接收两个函数, 要判断执行哪个)
then (onFULFILLED, onREJECT) {
onFULFILLED = typeof onFULFILLED == 'function' ? onFULFILLED : () =>{}
onREJECT = typeof onREJECT == 'function' ? onREJECT : () =>{}
// 处理在promise中加了setTimeout的时候的待定状态
if(this.status == Commitment.PENDING) {
this.resolveCallbacks.push(onFULFILLED)
this.rejectCallbacks.push(onREJECT)
}
if(this.status == Commitment.FULFILLED) {
//处理commitment.then中两个函数的异步操作(加个setTimeout())
setTimeout(() => {
//成功时执行resolve,并将前面result保留的数据传递过去
onFULFILLED(this.result)
})
}
if(this.status == Commitment.REJECT) {
setTimeout(() =>{
//成功时执行resolve,并将前面result保留的数据传递过去
onREJECT(this.result)
})
}
}
})
}
let commitment = new Commitment((resolve, reject)=>{
//在这里面处理异常
// throw new Error('出错了')
// 这里加setTimeout 这个时候还没有执行setTimeout中的resolve和reject就执行then方法了
// 所以要在上面then中处理状态为待定状态的操作
setTimeout(()=>{
//这里的resolve和reject是异步的,要加上setTimeout
resolve()
console.log('666')
})
})
commitment.then(
//处理这里不传入函数
result => {console.log(result)}
reject => {console.log(rejecr)}
)
防抖
//n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
<input type= 'submit' id='input'>
var btn = document.getElementById('input')
btn.addEventListener('click', debounce(submit,wait), false)
function submit () {
console.log('提交了')
}
//清空时间
function debounce (fn,wait) {
let timer = null
//点击事件会给submit传参
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(()=> {
//想要在定义的事件函数中获取到参数,所以要在这里传过去 这里的this指向btn
fn.apply(this,arguments)
}, wait)
}
}
节流
//函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
//判断时间
//时间戳
function throttle (fn,delay) {
let begin = 0
//点击事件会给submit传参
return function () {
var cur = Date.now()
if (cur-begin> delay) {
fn.apply(this,arguments)
begin = cur
}
}
}
//定时器
function throttle(fn, delay){
// 设置一个触发开关
let canUse = true
return function(){
//如果为true,就触发技能,否则就不能触发
if(!canUse) return
canUse = false
setTimeout(()=>{
fn.apply(this, arguments);
canUse = true;
}, delay)
}
}
手写call函数
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果
Function.prototype.myCall = function (context) {
if (typeof this !== 'function') {
return console.error('类型错误')
}
//判断context是否传入,没有传入则设置为window
context = context || window
//获取参数
let args = [...arguments].slice(1), result = null
context.fn = this
result = context.fn(...args);
delete context.fn
return result
}
手写apply函数
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性
- 返回结果
Function.prototype.mgApply = function (context) {
if (typeof this !== 'function') {
return consoel.error('类型错误')
}
context = context || window
let result = null
context.fn = this
if (arguments[1]) {
//arguments可以通过下标拿
result = context.fn(...arguments[1])
} else {
result = context.fn();
}
delete context.fn
return result
}
手写bind函数
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 保存当前函数的引用,获取其余传入参数值。
- 创建一个函数返回
- 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
return console.error('类型错误')
}
//拿参数 和call一样
let args = [...arguments].slice(1)],fn = this
return function Fn() {
// 如果被new调用,this应该是fn的实例
return fn.apply(this instanceof Fn ? this : context, args)
}
}
浅拷贝
(1)Object.assign()
该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。
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 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
- 该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。
let arr = [1,2,3,4]
console.log(arr.slice())//[1,2,3,4]
2)Array.prototype.concat
- 该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。
let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false
深拷贝
(1)JSON.stringify()
- 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过
JSON.stringify()进行处理之后,都会消失。
let obj1 = { a: 0, b: { c: 0 } };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
(2)函数库lodash的_.cloneDeep方法
var _ = require('lodash');
var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] };
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
(3)手写实现深拷贝函数
function deepClone(object) {
if (!object || typeof object !== 'object') return
let newObject = Array.isArray(object) ? [] : {}
for (let key in object) {
//这里要判断是不是这一层的属性,有可能下一层也有这个属性,所以要在这里判断一下
if(object.hasownProperty(key)) {
newObject[key] = typeof object[key] == 'object' ? deepClone(object[key]) : object[key]
}
}
return newObject
}
实现数组的扁平化
(1)递归实现
let arr = [1,[2,[3,4,5]]]
function flat(arr) {
let result = []
for(var i =0;i<arr.length;i++) {
if(Array.isArray(arr[i])){
result = result.concat(flatten(arr[i]))
}else {
result.push(arr[i])
}
}
return result
}
flat(arr)
(2)ES6 中的 flat
let arr = [1, [2, [3, 4]]];
function flat (arr) {
return arr.flat(Infinity)
}
console.log(flat(arr))
(3)some实现
let arr = [1, [2, [3, 4]]];
function flat(arr) {
while(arr.some((item)=>{Array.isArray(item)})) {
arr = [].concat(...arr)
}
return arr
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
实现数组的flat方法
function flat (arr, depth) {
if(!Array.isArray(arr) || depth<=0) return arr
return arr.reduce((prev, next) => {
if(Array.isArray(next)) {
return prev.concat(flat(next, depth - 1))
}else {
//注意这里是concat不是push
return prev.concat(next)
}
},[])
}
实现数组的push方法
//返回长度
let arr= []
Array.prototype.push = function (){
for(var i = 0;i<arguments.length;i++) {
this[this.length] = arguments[i]
}
return this.length
}
实现数组的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;
}
实现数组的filter方法
Array.prototype.filter = function(fn) {
if(typeof fn !== 'function') return new Erroe('参数必须是一个函数')
let res = []
for(var i = 0;i<this.length;i++) {
//filter返回成功的那个元素所以要执行返回true后再push
fn(this[i]) && res.push(this[i])
}
return res
}
实现数组的reduce方法
Array.prototype.reduce = function(fn, init) {
var total = init || arr[0] // 有初始值使用初始值
// 有初始值的话从0遍历, 否则从1遍历
for (var i = init ? 0 : 1; i < this.length; i++) {
//reduce中函数的参数接受4个值
total = fn(total, this[i], i , this)
}
return total
}
var arr = [1,2,3]
console.log(arr.reduce((prev, item) => prev + item, 10))
实现数组的every方法
let arr= [1,2,3]
Array.prototype.every = function (fn) {
if(typeof fn!== 'function') return new Error('参数必须是函数')
for(var i =0;i<this.length;i++) {
if (!fn(this[i])) { return false}
}
return true
}
let my = arr.every((item) => {
return item>1
})
console.log(my)
实现数组的some方法
let arr= [1,2,3]
Array.prototype.some = function (fn) {
if(typeof fn!== 'function') return new Error('参数必须是函数')
for(var i =0;i<this.length;i++) {
//some是返回true,而find是返回item,findIndex是返回i
if (fn(this[i])) { return true}
}
//some是返回false,而find和findIndex是返回undefined
return false
}
let my = arr.some((item) => {
return item>1
})
console.log(my)
实现数组的forEach方法
forEach里面如果修改基本数据类型要通过arr[index]的方式,因为保存的值,可以修改引用数据类型 forEach如果没有第二个参数默认this指向widow,如果有则指向这个对象
参考 深入理解关于forEach、map等循环方法无法修改当前遍历值 - 掘金 (juejin.cn)
let arr = ['a', 'b', 'c', 'd', 'e', 'f', 100]
Array.prototype.myForeach = function (fn) {
// 如果不是函数 抛异常
if (!Object.prototype.toString.call(fn) == '[object Function]') {
throw new Error(`${fn} is no a function`)
}
let param = this
for (var i = 0; i < param.length; i++) {
fn(param[i], i, param)
}
}
arr.myForeach((item, index, param) => {
console.log(item, index, param)
return (arr[index])
})
console.log(arr)
实现数组去重
ES6方法(使用数据结构集合)
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(array)])
ES5方法:使用map存储不重复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
function uniqueArr(arr) {
let result = [],map = {}
for(var i =0;i<arr.length;i++) {
if(!map[arr[i]]) {
map[arr[i]] =1
}
}
console.log(map)
for(var j in map) {
if (map[j]) {
result.push(j*1)
}
}
return result
}
console.log(uniqueArr(array))
大数相加
function sumBigNumber(a, b) {
let res = '';
let temp = 0;
a = a.split('');
b = b.split('');
//[] || [] 为true
while (a.length || b.length || temp) {
// [].pop()为undefined ~NaN为-1 ~-1位0 对计算没有影响
temp += ~~a.pop() + ~~b.pop();
res = (temp % 10) + res;
// 判断是否有进位
temp = temp > 9
}
return res.replace(/^0+/, '');
}
sumBigNumber('546546544665464645','5646465446546')
函数柯里化add(1)(2)(3)
1.直接写
function add(a){
return function(b){
return function (c){
return a+b+c
}
}
}
console.log(add(1)(2)(3))
2.封装一个公用的
function add(a,b,c){
return a+b+c
}
function curry(fn) {
//获取fn参个数
let len = fn.length
return function temp(){
let args = Array.prototype.slice.call(arguments)
if (args.length >= len) {
return fn(...args)
} else {
return function () {
return temp(...args,...arguments)
}
}
}
}
let r = curry(add)
console.log(r(9)(2)(3))
将js对象转化为树形结构
// 转换前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}
}]
代码实现:
function jsonToTree(data) {
// 初始化结果数组,并判断输入数据的格式
let result = []
if(!Array.isArray(data)) {
return result
}
// 使用map,将当前对象的id与当前对象对应存储起来
let map = {};
data.forEach(item => {
map[item.id] = item;
});
data.forEach(item => {
//根据元数据的pid得到父元素
let parent = map[item.pid];
if(parent) {
(parent.children = []).push(item);
} else {
result.push(item);
console.log(result)
}
});
return result;
}
console.log(jsonToTree([{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
),'00')
用Promise实现图片的异步加载
function imageAsync(url) {
return new Promise((resolve,reject) => {
let img = new Image()
img.src = url
img.onload = () => {
console.log('图片加载成功')
resolve()
}
img.onerror = () =>{
console.log('图片加载失败')
reject()
}
})
}
imageAsync(url).then(
()=>{console.log('加载成功')},
()=>{console.log('加载失败')}
)
### 实现双向数据绑定
Object.defineProperty方式
js
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})