1、手写Object.create方法
作用:用于创建一个以传递的对象作为原型的对象。
思路:将传入的对象作为原型
// Object.create 创建一个以obj为原型的对象
function create(obj){
// 创建一个空对象
function F(){}
// 其原型为传递的对象
F.prototype = obj
// 返回对象
return new F()
}
2、手写instanceof方法
作用:判断构造函数的prototype属性是否出现在对象的原型链中任何位置。
思路:
- 获取类型的原型
- 获取对象的原型
- 循环对象的原型是否等于类型的原型,直到对象原型为
null
function myinstanceof(left, right) {
// 获取
let prototype = right.prototype
let proto = Object.getPrototypeOf(left)
// 判断原型是否存在
while (true) {
// 如果原型为null说明还没找到
if (proto == null) return false
// 如果相同则返回true
if (proto == prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
3、手写new操作符
思路:
- 创建一个空对象
- 判断第一个参数是否为构造函数
- 创建一个以构造函数原型为原型的对象并赋给空对象
- 执行构造函数使this执行新对象
- 返回结果
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
return this.name
}
function objectFactory() {
// 创建一个空对象
let newObj = null
// 获取arguments的第一项
let constructor = Array.prototype.shift.call(arguments);
// 判断传递的第一个参数是否为函数
if (typeof constructor !== 'function') {
console.error("type error");
return
}
// 获取构造函数的原型,并赋值给新对象
newObj = Object.create(constructor.prototype)
// 执行构造函数
let result = constructor.apply(newObj, arguments)
// 判断返回结果是否是对象
return result instanceof Object ? result : newObj
}
let p = objectFactory(Person, "hwm", 1)
4、手写防抖函数
思路:事件被触发,n秒后再执行回调,如果在这n秒内事件又被触发了,则重新计时。避免因为用户多次输入而触发事件多次。
// 防抖函数
function debounce(fn) {
let timer = null
// 返回一个函数
return function () {
// 获取触发的对象
let context = this
// 获取参数
let args = arguments
// 判断是否开启定时器
if (timer) {
clearInterval(timer)
timer = null
}
// 设置定时器
timer = setTimeout(() => {
// 执行函数
fn.apply(context, args)
}, 500)
}
}
// 输入框的回调函数
function myInp(e) {
console.log(inp.value);
}
var inp = document.querySelector('#inp')
// 经过防抖处理函数
inp.oninput = debounce(myInp)
5、手写节流函数
思路:事件被触发,n秒后再执行回调,如果在该时间内在次触发,事件不会生效,直到n秒后,再触发才会生效。
function throttle(fn) {
// 定义定时器
let timer = null
// 返回一个函数
return function () {
// 保存触发该函数的对象
let context = this
// 保存参数
let args = arguments
// 判断是否已经开启定时器
if (timer) {
// 如果开启了定时器则直接返回不触发函数
return
}
// 设置定时器
timer = setTimeout(() => {
fn.apply(args)
// 执行完,将timer重新赋值
timer = null
}, 500)
}
}
// 点击事件回调
function MyBtn(e) {
console.log('被点击');
}
var btn = document.querySelector('#btn')
btn.onclick = throttle(MyBtn)
6、手写判断类型
思路:
- 如果是null或undefined直接返回本身的字符串形式
- 如果是引用类型,通过
Object.prototype.toString.call
判断 - 如果基本类型,通过
typeof
来判断
function getType(value) {
// 判断value是否为null undefined
if (value === null || value == undefined) {
// 隐式转换 转为字符串
return value + ""
}
// 判断是不是对象类型
if (value instanceof Object) {
// 再准确的判断类型
let valueClass = Object.prototype.toString.call(value)
// [object Array] 截取后面的类型
let type = valueClass.split(" ")[1].split("]")[0].toLowerCase();
// 返回类型
return type
}else {
// 基本类型
return typeof value
}
}
7、手写call方法
思路:
- 先判断调用对象是否为函数
- 判断上下文对象是否合法
- 给上下文对象绑定方法
- 调用上下文方法
Function.prototype.myCall = function (context, ...args) {
// 判断调用对象是否为函数
if (typeof this !== 'function') {
return
}
// 判断出入的上下文
context = context || window
// 创建唯一标识
let key = Symbol('KEY')
// 给上下文添加该方法的属性
context[key] = this
// 调用该方法
let result = context[key](...args)
// 删除该属性
delete context[key]
// 返回结果
return result
}
let obj = {
name: 'hwm'
}
function fn(a,b) {
console.log(this);
console.log(a,b);
}
fn.myCall(obj,1,2)
8、手写apply方法
思路:
- 判断调用对象是否为函数
- 判断上下文是否合法
- 给上下文对象绑定方法
- 调用上下文方法
Function.prototype.myApply = function(context,args){
// 判断调用对象是否为函数
if(typeof this !== 'function'){
return
}
// 判断上下文是否合法
context = context || window
// 唯一标识
let key = Symbol('KEY')
// 给上下文添加该属性
context[key] = this
// 调用该函数
let result = context[key](...args)
// 删除属性
delete context[key]
return result
}
let obj = {
name: 'hwm'
}
function fn(a,b) {
console.log(this);
console.log(a,b);
}
fn.myApply(obj,[1,2])
9、手写bind方法
思路:
- 判断调用对象是否为函数
- 判断上下文对象是否合法
- 保存调用对象
- 返回一个函数
- 在返回函数中给上下文对象绑定方法
- 调用上下文方法
- 删除该方法
Function.prototype.mybind = function (context, ...args) {
if (typeof this !== 'function') {
return
}
// 保存函数
let fn = this
context = context || window
return function () {
let key = Symbol('KEY')
context[key] = fn
let result = context[key](...args)
delete context[key]
return result
}
}
let obj = {
name: 'hwm'
}
function fn(a, b) {
console.log(this);
console.log(a, b);
}
let a = fn.mybind(obj, 1, 2)
a()
10、实现浅拷贝
如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址
Object.assign
注意:
- 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
- 因为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}
扩展运算符
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}}
数组方法
Array.prototype.slice
let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() === arr); //false
Array.prototype.concat
let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false
手写实现浅拷贝
思路:
- 判断拷贝的对象是否合法
- 根据拷贝对象的类型生成一个空对象
- 遍历对象的属性,并给新对象添加
function shallowCopy(object){
// 判断传入的对象是否合法
if(!object || typeof object !== 'object') return
// 根据object类型生成一个同类型的空对象
let newObj = Array.isArray(object) ? [] : {}
// 遍历对象
for(let key in object){
// 判断属性是否存在对象上
if(object.hasOwnProperty(key)){
// 只对第一次属性进行拷贝
newObj[key] = object[key]
}
}
return newObj
}
let obj1 = {
name:'hwm',
color:[1,2,3]
}
let obj2 = shallowCopy(obj1)
obj2.name = "obj2"
obj2.color.push(4)
console.log(obj1);
console.log(obj2);
11、实现深拷贝
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}}
函数库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
手写实现深拷贝
思路:
- 判断拷贝对象是否合法
- 根据拷贝对象类型生成一个空对象
- 遍历拷贝对象
- 判断属性值是否为对象,如果是则递归该函数
function deepCopy(object) {
if (!object || typeof object !== 'object') return
let newObj = Array.isArray(object) ? [] : {}
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObj[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]
}
}
return newObj
}
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
let obj2 = deepCopy(obj1)
obj2.b.f.g = 3
obj2.c.push(4)
console.log(obj1);
console.log(obj2);
12、数组push方法
思路:
- 遍历传入的参数
- 以数组长度为下标赋值为每一项参数
let arr = [1,2,3]
Array.prototype._push = function () {
for (let i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i]
}
}
arr._push(4,5)
13、数组filter方法
思路:
- 遍历数组每一项,将每一项都传入到回调函数中
- 返回值为true,则放进新数组中
Array.prototype._filter = function (fn) {
// 判断fn是不是函数
if (typeof fn !== 'function') {
return
}
// 保存数组
let self = this
let res = []
for (let i = 0, len = self.length; i < len; i++) {
// 返回值结果为true则放进数组
if (fn(self[i])) {
res.push(self[i])
}
}
return res
}
let arr = [1, 2, 3, 4, 5]
let a = arr._filter((item) => {
if (item > 3) {
return true
}
})
14、数组的map方法
思路:
- 遍历数组每一项,将每一项都传入到回调函数中
- 将返回值放进新数组中
Array.prototype._map = function (fn) {
if (typeof fn !== "function") {
return
}
// 保存数组
let self = this
let res = []
for(let i = 0; i < self.length; i++){
// 将经过fn处理的结果放进数组
res.push(fn(self[i]))
}
return res
}
let arr = [1,2,3]
let a = arr._map((item)=>{
return item += 1
})
15、将js对象转换为树形结构
思路:
- 判断传入的参数是否为数组
- 遍历每一项,以map将id作为key元素本身作为value保存起来
- 遍历每一项,获取每一项的父元素id
- 判断父元素是否存在ma对象中,存在则判断是否存在child属性,不存在则添加属性,并将当前项添加进数组中
- 如果父元素不存在map中,则将该元素添加进结果数组中
let source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
function jsonTree(data) {
// 返回结果
let result = []
// 判断参数是否为数组
if (!Array.isArray(data)) {
return result
}
// 使用map将数组存储起来
let map = {}
data.forEach(item => {
map[item.id] = item
})
data.forEach(item => {
// 获取到每一项的父元素
let parent = map[item.pid]
// 给父元素添加孩子属性
// console.log(parent);
if (parent) {
// 判断父元素是否有child属性
if (!parent.child) {
parent.child = []
}
// 将该item项添加到父元素的child属性中
parent.child.push(item)
}else {
// 只有一个为根所以只有一个元素的父元素为undefined
result.push(item)
}
})
return result
}
let tree = jsonTree(source)