1.手写call
Function.prototype.myCall = function (context,...rest) {
//检测改变后的上下文对象的类型
var type = typeof context;
//判断改变后的上下文对象是null和undefined的时候 this应该指向window
if (context === null || context === undefined) {
context = window;
}
//如果改变后的上下文对象是基本包装类型,则this指向其包装对象
switch (type) {
case "number":
context = new Number(context);
break;
case "boolean":
context = new Boolean(context);
break;
case "string":
context = new String(context);
break;
}
//rest就是要传递给函数的参数,只不过是一个数组类型
console.log(rest)
//因为fn1.myCall调用,所以这里的this指向的就是fn1
//context就是改变之后的上下文对象
//给context扩展一个方法,这个方法就是fn1,
//给context扩展的方法名要是一个独一无二的值,防止覆盖原有方法
var key = Symbol();
context[key] = this;
//然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context,传入参数
const result = context[key](...rest);
//此时改变之后的上下文对象context就会多一个方法,所以使用完成之后要删除掉这个方法
delete context[key];
//函数的返回值
return result;
}
2.手写bind
Function.prototype.mybind = function (obj) {
if (obj === undefined || obj === null) {
obj = window;
}
obj = Object(obj);
obj.fn = this;
return function (...args) {
obj.fn(...args);
delete obj.fn;
};
};
function fn(a, b, c) {
console.log(this, a, b, c);
}
fn.mybind({ name: "hello" })(1, 2, 3);
3.手写apply
//类似于call
Function.prototype.myApply = function (context,...rest) {
var type = typeof context;
//判断改变后的上下文对象是null和undefined的时候 this应该指向window
if (context === null || context === undefined) {
context = window;
}
//如果改变后的上下文对象是基本包装类型,则this指向其包装对象
switch (type) {
case "number":
context = new Number(context);
break;
case "boolean":
context = new Boolean(context);
break;
case "string":
context = new String(context);
break;
}
//因为fn1.myCall调用,所以这里的this指向的就是fn1
//context就是改变之后的上下文对象
//给context扩展一个方法,这个方法就是fn1,
var key = Symbol();
context[key] = this;
//然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
var result = context[key](...rest[0]);
//此时改变之后的上下文对象context就会多一个方法,所以使用完成之后要删除掉这个方法
delete context[key];
return result;
}
4.防抖
//真正的事件发生时的逻辑代码
function inputChange(e) {
console.log("表单改变 请求数据");
console.log(e);
console.log(this);
}
oIpt.oninput = debounce(inputChange, 800)
//防抖函数
function debounce(fn, time) {
//初始化一个计时器
var timer = null;
//事件函数
return function () {
//每次触发的时候,先把上一次的未执行的计时器清掉,然后重新开始计时(那么上一次的没有执行的逻辑函数就不会再执行了,而是重新计时)
clearTimeout(timer)
//在计时器中 arguments是不符合的,需要使用这个位置的arguments,需要保存起来
var arg = arguments;
//保存外边的this 在计时器函数中使用
var that = this;
//每次触发事件,先不执行,要延迟一定的时间再执行
timer = setTimeout(function () {
fn.call(that, arg[0]);
}, time)
}
}
5.节流
/*
函数的节流(throttle)与防抖(debounce)
作用:为了节约函数的性能(让函数调用次数更少)
节流(throttle):让函数在单位时间内只调用一次,第一次调用生效
应用场景:发送验证码按钮
防抖(debounce):让函数在单位时间内只调用一次,最后一次调用生效
应用场景:搜索栏
*/
//move才是真正的事件发生时的逻辑代码
function move(e) {
//以下是真正逻辑代码code
console.log(1);
console.log(e);
console.log(this);
}
//把move传递给节流函数,节流函数调用以后 返回一个新函数 赋值给move事件
oBox.onmousemove = throttle(move, 200);
function scroll() {
console.log("滚了")
}
window.onscroll = throttle(scroll, 300)
//节流函数(高阶函数)
function throttle(fn, time) {
//绑定事件的时候,先初始化一个上一次的时间(第一次的话没有上一次,所以初始化时间为0即可)
var lastTime = 0;
//这个函数是事件触发的时候真正调用的事件函数
return function () {
//这个函数就负责书写看门狗,当允许通过的时候 再调用真正的逻辑代码move
var nowTime = Date.now();
if (nowTime - lastTime < time) {
return;
}
lastTime = nowTime;
//arguments所在的函数就是真正的事件函数,所以拥有实参event 把event事件对象传递给fn move中就可以使用event事件对象了
// fn(arguments[0]);
//改变了fn的this为事件触发的对象
fn.call(this, arguments[0])
}
}
6.深拷贝
//方案1
function deepClone(obj) {
//判断类型 如果是基本类型 则直接返回 如果是对象类型,则开始拷贝
if (checkType(obj) === 'object') {
var newObj = {};
} else if (checkType(obj) === 'array') {
var newObj = [];
} else {
return obj;
}
//拷贝
for (var key in obj) {
//每次拷贝之前 把拷贝的递归一下,如果是基本值,则直接返回,否则再次拷贝
newObj[key] = deepClone(obj[key]);
}
return newObj;
}
//方案2
//不能拷贝方法
var re = JSON.parse(JSON.stringify(obj1))
7.new操作符
/*
手写new思路:
1.声明一个对象obj,作为new的返回值(实例化对象)
2.调用构造函数,并且把构造函数的this指向obj
3.修改obj的隐式原型为构造函数的显示原型
4.判断构造函数的返回值类型,如果是基本类型则正常返回obj,否则返回构造函数的返回值
*/
function myNew(FN) {
//声明一个对象obj,作为new的返回值(实例化对象)
var obj = {};
//调用构造函数,并且把构造函数的this指向obj
var FNReturn = FN.apply(obj, Array.from(arguments).slice(1));
//修改obj的隐式原型为构造函数的显示原型
obj.__proto__ = FN.prototype;
//判断类型是object 并且不是null
if (typeof FNReturn === 'object' && FNReturn != 'null') {
return FNReturn;
}
//判断是function
if (typeof FNReturn === 'function') {
return FNReturn;
}
//基本类型值
return obj;
}
8.实现一个instanceof
function myInstanceof(A, B) {
var BPro = B.prototype;
var startA = A.__proto__;
//如果while条件达不到,则说明B不在A的原型链上 返回false
while (startA) {
if (startA === BPro) {
return true;
}
//每次要获取上一级的原型对象
startA = startA.__proto__;
}
return false;
}
9.手写数据代理
// 定义Vue内部的实现
function Vue(options) {
// 将options中的data保存到Vue实例的_data上
this._data = options.data
// 通过defineProperty给vue实例添加data中有所有属性
// 遍历data中的所有属性, 分别去实现数据代理
Object.keys(this._data).forEach(key => { // key就是msg/msg2
// 给vue实例添加带getter和setter的属性
Object.defineProperty(this, key, {
// 在getter中: 读取并返回data对象中对应的属性值
get () {
console.log('getter')
return this._data[key]
},
// 在setter中: 将最新的值保存到data对象的对应属性上
set (value) {
console.log('setter')
this._data[key] = value
}
})
})
}
10.手写数据劫持
// 定义Vue内部的实现
function Vue(options) {
// 初始对data对象中所有层次属性的数据劫持(实现响应式数据)
this._defineReactive(this._data)
}
Vue.prototype = {
/*
对data中所有层次属性实现数据劫持(也就是定义响应式属性)
*/
_defineReactive (data) {
// 如果data不是对象, 直接结束
if (data==null || typeof data != 'object') return
Object.keys(data).forEach(key => {
// 取出对应的属性值
let value = data[key]
// 通过递归调用, 实现对data内部对象的深度劫持
this._defineReactive(value)
// 给data中的key属性通过defineProperty添加getter和setter
Object.defineProperty(data, key, {
get () {
console.log(`监视到读取data的${key}属性`)
// return data[key] // 会导致get执行, 死循环了
return value
},
set (newValue) {
// 保存最新的值
value = newValue
console.log(`监视到data的${key}属性变为了${newValue}`)
console.log('准备去更新DOM')
}
})
})
},
}