JS之手写XX
手动实现bind、apply及bind
- 实现call()
// 实现call方法
Function.prototype.myCall = function(obj) {
const args = [...arguments].splice(1); //拿到除了第一个参数外的所有参数,并使其变成数组。
obj.temp = this; // 这里的this其实就是调用mycall的函数,我们把这个函数赋值给了obj的一个临时变量。
obj.temp(...args); // 这个时候我们执行obj.temp 函数的this就变成了了obj。
delete obj.temp; // 销毁函数避免作用域污染
}
- 实现apply()
// 实现apply方法
Function.prototype.myApply = function(obj,arr) {
obj.temp = this;
obj.temp(...arr);
delete obj.temp;
}
- 手写bind()
//实现bind方法
// bind有使用有两个方式,实现起来稍微复杂一点,bind的参数可以在使用bind时传,也可以在返回的函数中传.
// 1.
const action = test.bind(obj, 1)
action() // 1,'zs'
// 2.
const action = test.bind(obj)
action(1) // 1,'zs'
Function.prototype.myBind = function(obj,arr) {
let args = [...arguments].splice(1) // 调用myBind时传入参数,状态1;
const self = this; // 保存一下这个this,为当前调用函数test;
return function() {
const inArgs = [...arguments] // 返回函数的参数,状态2;
if(arguments.length > 0) {
args = [...args,...inArgs]; //把两次传入的值都放在这个数组里;
}
self.apply(obj,args);
}
}
const action = test.myBind(obj, 1)
action() // 1,'zs'
const action = test.myBind(obj)
action(1) // 1,'zs'
手动实现new
// 实现new方法
function createNew() {
var obj = {} // 1.创建一个空对象
let constructor = [].shift.call(arguments); // 参数第一个为constructor
obj.__proto__ = constructor.prototype; // 2.链接到原型
let result = constructor.apply(obj,arguments); // 3.绑定this值
return typeof result === 'object'?result:obj; // 4.返回新对象
}
手动实现浅拷贝、深拷贝
// 浅拷贝
let copy1 = Object.assign({},{x:1});
// 深拷贝
// 缺点:拷贝对象包含 正则表达式,函数,或者undefined等值会失败
let obj = {a:1,b:{x:3}};
let obj1 = JSON.parse(JSON.stringify(obj));
// 使用递归
function deepCopy(obj) {
let copy = obj instanceof Array ? [] : {};
for(let i in obj) {
// JS hasOwnProperty()方法:检测一个属性是否是对象的自有属性
if(obj.hasOwnProperty(i)) {
copy[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
}
return copy;
}
手动实现节流、防抖
const throttle = function(fn,delay,isBebounce) {
let timer;
let lastCall = 0;
return function(...args) {
if(isBebounce) {
//函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(function(){
fn.apply(this,...args);
},delay)
} else {
//当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
var prev = new Date();
if(prev - lastCall > delay) return
lastCall = prev;
fn.apply(this,...args);
}
}
}
手动实现instanceof原理
// instanceof原理:右边变量的原型存在于左边的原型链上
function instanceOf(left,right) {
let leftValue = left.__proto__;
let rightValue = right.prototype;
while(true) {
if(leftValue === null) {
return false
}
if(leftValue === rightValue) {
return true;
}
leftValue = leftValue.__proto__;
}
}
手动实现Object.create原理
// Object.create 的基本实现原理
// 思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
手动实现数据双向绑定
// 实现一个双向数据绑定
let obj ={}
let input = document.getElementById('input');
let span = document.getElementById('span');
// 数据劫持
Object.defineProperty(obj,'text', {
configurable:true, //configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。
enumerable:true, //是否可通过for-in循环,flase为不可循环,默认值为true
get() {
console.log('获取数据了');
},
set(newVal) {
console.log('数据更新了');
input.value = newVal;
span.innerHTML = newVal;
//innerText主要是设置文本的,甚至标签内容,是没有标签的效果的。innerHTML主要的作用是在标签中设置新的heml标签内容,是有标签效果的
}
})
//输入监听
input.addEventListener('keyup',function(e) {
obj.text = e.target.value;
})
手动实现ajax
//实现一个ajax
//方法1
var xhr = new XMLHttpRequest()
// 初始化
xhr.open(method,url,async);
//发送请求
xhr.send(data)
//设置状态变化回调处理请求结果
shr.onreadystatechange = () => {
if(xhr.readyStatus === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
}
// 方法二基于promise实现
function ajax(options) {
const url = options.url;
const method = options.methods.toLocaleLowerCase() || 'get';
const async = options.async; // 默认异步
const data = options.data;
const xhr = new XMLHttpRequest(); //实例化
if(options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout;
}
//返回一个promise实例
return new Promise((resolve,reject) => {
xhr.ontimeout = () => reject && reject('请求超时');
xhr.onreadystatechange =()=>{
if(xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if(xhr.status >=200 && xhr.status < 300 || xhr.status === 304) {
resolve && resolve(xhr.responseText);
} else {
reject && reject()
}
}
}
xhr.onerror = err=> reject && reject(err); // 错误回调
let paramArr = []
let encodeData
// 处理请求参数
if (data instanceof Object) {
for (let key in data) {
// 参数拼接需要通过 encodeURIComponent 进行编码
paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
encodeData = paramArr.join('&')
}
// get请求拼接参数
if (method === 'get') {
// 检测url中是否已存在 ? 及其位置
const index = url.indexOf('?')
if (index === -1) url += '?'
else if (index !== url.length -1) url += '&'
// 拼接url
url += encodeData
}
// 初始化
xhr.open(method, url, async)
// 发送请求
if (method === 'get') xhr.send(null)
else {
// post 方式需要设置请求头
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
xhr.send(encodeData)
}
})
}
手动实现await sleep函数
//实现一个 await sleep函数延迟n秒后执行后面的语句
//一般情况下,await 后面是一个promise对象,如果不是,会被立即转成一个resolve的promise对象
function sleep(time) {
return new Promise(resolve=>{
setTimeout(resolve,time)
})
}
手动实现如何渲染大数量级的数据的方法
//当面临上千万记录要显示的时候,如何处理呢?
//分页显示(分页查询)
//分批插入
//异步里面调用loop(),开启一个任务线程,避免影响js引擎或渲染线程
setTimeout(()=>{
// 插入十万条数据
const total = 100000;
// 一次插入200条
const once = 200;
const loopCount = total / once;
let countRender = 0;
let ur = document.querySelector('ul');
function add() {
// 优化性能,插入不会引起回流
// 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。
const fragment = document.createDocumentFragment();
for(let i =0; i < once; i++) {
const li = document.createElement('li');
li.innerText = `这是第${countRender}次的li${li}`
fragment.appendChild(li);
}
ul.appendChild(fragment);
countRender += 1;
loop()
}
function loop() {
while(countRender < loopCount) {
window.requestAnimationFrame(add())
}
}
loop();
},0)
手动实现jsonp
// jsonp解决跨域
// 动态创建<script>标签
// 基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function() {
// ?callback=foo 返回一个回调函数
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('response data:' + JSON.stringify(data));
}
// 服务器端会在返回的参数外面加一个对应的函数包裹层
foo({
"test": "testData"
});
手写add(1)(2,3)(5)(柯里化实现)
function add() {
var args = Array.prototype.slice.call(arguments);
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
if(arguments.length == 0) {
var sum = 0;
for(var i in args) {
sum += args[i];
}
return sum;
} else {
var finalArgs = args.concat(innerArgs);
return add.apply(this, finalArgs)
}
}
};
console.log(add(1)(2,3)(5)()); // 11
如何柯里化一个函数?
function curry(func) {
return function curried(...args) {
// 参数够了,执行本来的func
if(args.length >= func.length) {
return func.apply(this,args)
} else {
// 参数不够,就‘保存’下参数,返回一个新的function。
return curried.bind(this,...args)
}
}
}
手写Promise.all()
// Promise.all()
// 返回一个Promise,当所有的promise fulfill的时候结果为fulfill;当任意一个promise reject 结果reject。
function myPromiesAll(promises) {
// 当输入的promises中含有一些非Promise数据时(Promise.all()支持传入非Promise数据),我们需要将非Promise数据转化为Promise数据。
const _promises = promises.map((item)=>item instanceof Promise?item:Promise.resolve(item));
if(_promises.length === 0){
return Promise.resolve([]);
}
return new Promise((resolve,reject)=>{
// fulfill的数据需要放再一个数组里,但是promise的fulfill时机未知,先后顺序不定,所以不能用push存数据,而是利用index来放置数据到正确的位置。
// 但是这样做过后,存放的数据的数组不能通过length来判断是否所有的promise都已经fulfill了,因此需要一个变量fulfilledCount来计数。
const result = [];
let fulfilledCount = 0;
let isErrored = false;
_promises.forEach((promise,index)=>{
promise.then((value) => {
//
if(isErrored) return
result[index] = value;
fulfilledCount+=1;
if(fulfilledCount == _promises.length) {
resolve(result);
}
},(error)=>{
// 处理reject的时候直接reject promise即可。单是这里需要注意的是,reject之后,依然可能有promise继续fulfill或者reject,我们需要避免这些情况的发生,因此使用了isErrored;
if(isErrored) return
isErrored = true;
reject(error)
})
})
})
}
学习于奔跑的瓜牛:JS高频手写代码
什么是柯里化?看下这个哦:函数绑定与函数柯里化