手写代码
- 1、手写instanceof方法
- 2、手写new操作符
- 3、手写Promise.all()
- 4、手写防抖函数
- 5、手写节流函数
- 6、手写call、apply函数
- 7、手写bind函数
- 8、封装ajax
- 9、手写深拷贝
- 10、实现数组扁平化
1、手写instanceof方法
instanceof运算符用于判断构造函数的prototype属性是否出现在对象的原型链中的任何位置。
实现的步骤:
1、首先获取对象的原型
2、然后获取构造函数的原型
3、然后一直循环判断构造函数的原型是否等于对象的原型,直到对象原型为null,因为原型链的最终为null
<script>
/* 手写 instanceof方法 */
function myInstanceof(left,right){
// 1、获取对象的原型
var proto = Object.getPrototypeOf(left);
// 2、获取构造函数的原型对象
var obj = right.prototype;
// 3、循环判断对象的原型是否等于构造函数的原型,直到对象原型为null,因为原型链最终为null
while(true){
if(!proto) return false;
if(obj === proto) return true;
proto = Object.getPrototypeOf(proto); // 再取对象原型的原型
}
}
console.log(myInstanceof(133,String)) // false 因为133是数字类型,如果改为字符串就对了
</script>
2、手写new操作符
在调用 new 的过程中会发生以上四件事情:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
<script>
/* 手写new操作符 */
function objectFunction(){
// 初始化
let newObj = null;
// shif()方法是从数组中删除第一个元素,并返回该元素的值
// constructor 获取arguments中的第一个参数
let constructor = Array.prototype.shift.call(arguments)
// 判断是否为构造函数
if(typeof constructor !== 'function'){
console.log("error!");
return;
}
/* new操作符过程 */
// 1、创建一个新对象并且把构造函数的原型赋给新对象
newObj = Object.create(constructor.prototype); // 类似 newObj={};newObj.__proto__ = constructor.prototype
// 2、改变构造函数的this指向,并执行函数
let result = constructor.apply(newObj,arguments);
// 3、根据result结果返回.如果result是个引用数据类型 则返回,如果不是则忽略返回newObj
if(result &&(typeof result === 'object' || typeof result === 'function')){
return result;
}else{
return newObj;
}
// 使用方法
objectFunction(构造函数,初始化参数)
}
</script>
3、手写Promise.all()
核心思路:
1、接受一个Promise实例的数组或具有Iterator接口的对象作为参数;
2、这个方法返回一个新的promise对象;
3、遍历传入的参数,用promise.resolve()将参数“包一层”,使其变成一个promise对象;
4、参数所有回调才是成功,返回值数组与参数顺序一致;
5、参数数组其中一个失败,则触发失败的Promise错误作为Promise.all的错误
<script>
/* 手写Promise.all */
var promiseAll = function (promise) {
return new Promise(function (resolve, reject) {
/* 判断传递过来的参数是否为数组 */
if (!Array.isArray(promise)) {
throw new TypeError("参数必须是数组")
}
let len = 0;//用来记录数组中每个promise成功状态的的长度
let result = [];// 用来记录数组中每个promise的结果
let arrLen = promise.length; // 记录参数数组的长度
for (let i = 0; i < arrLen; i++) {
/* Promise.resolve() 将现有对象转为Promise对象,使用then来执行 */
Promise.resolve(promise[i]).then(value => { // 成功的回调
len++;
// result.push(value);
result[i] = value
if (len == arrLen) {
return resolve(result)
}
}, error => { // 失败的回调
return reject("error")
})
}
})
}
// 测试
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
// 返回的顺序就是你传参的顺序
promiseAll([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
})
</script>
4、手写防抖函数
let input =document.querySelector('input');
// 调用封装好的防抖
input.oninput = debounce(function(){
console.log(this.value)
},500)
/* 封装防抖 */
// 防抖:在规定时间后才执行,如果在这段时间内有触发,是会重新计算,只执行最后一次
function debounce(fn,wait){
let time = null; // 初始一个定时器
return function(){
/* 如果发现之前有定时器,就清空 */
if(time != null){
clearTimeout(time); // 清除定时器
}
time = setTimeout(()=>{
// console.log(this); // 这里的this指的是 input事件
// 参数传递过来的fn this指向是window对象,我们需要让它指向input事件
// 这里使用 call()改变指针指向,让fn寻找input为主人,这样input就可以使用fn函数
fn.call(this) // 这里面的this指的就是 input事件
},wait)
}
}
</script>
5、手写节流函数
<style>
body{
height: 2000px;
}
</style>
<body>
<script>
/* 使用滚动条来触发 节流 */
window.onscroll = throttle(function(){
console.log("触发滚动条")
},500)
/* 封装节流 */
// 节流:一段时间内只会执行一次,将频繁操作转换为少量操作
function throttle(fn,delay){
let flag = true; // 标记
return function(){
if(flag){
// 设置一个定时器
setTimeout(()=>{
// 这里跟防抖是一样的,要让fn找滚动条事件为主人,改变fn的this指向
fn.call(this); // 这里的this指的是 滚动条事件
/* 这里为什么要设置flag=true? */
/* 就好比一个接单兼职,刚开始是没有单子做,所以刚开始flag=true,表示我可以接单,一次接一个单子
我要等 delay时间才能完成,这时就把牌牌flag=false,表示我还在完成单子任务,当我完成之后,我就把
flag=true,继续开始接单。
*/
// 这样去记忆会好些
flag = true;
},delay)
}
flag = false;
}
}
</script>
6、手写call、apply函数
手写call的实现步骤:
1、判断调用对象是否为函数
2、判断传入上下文对象是否存在,如果不存在,则设置为window
3、处理传入的参数,slice截取第一个参数后的所有参数
4、将函数作为上下文对象的一个属性
5、使用上下文对象来调用这个方法,并保存返回结果
6、删除刚才新增的属性
7、返回结果
apply函数的实现步骤与call函数的实现步骤其实差不多,只是处理传入参数那步不太同,注释我都在代码里面写的很清楚,如果还有不懂得地方,大家也可以给我留言
<script>
/* 手写call函数 */
Function.prototype.myCall = function(obj) {
/* 这里的this我猜应该是函数调用者 */
if (typeof this != 'function') {
console.error("调用者不是函数")
}
// 如果没有传值过来就是window
obj = obj || window; // obj 代表第一个参数
let arr = [...arguments].slice(1); // 获取从第二个参数后面的数据
obj.fn = this; // 给我们所在的obj函数添加的fn变量赋值this,而这个this就是我们调用myCall的函数
// 执行 相当于 把arr参数传给调用myCall函数上去执行
let result = obj.fn(...arr) // 扩张运算符 ... 可以把数组转为参数序列
delete obj.fn; // 删除我们自己所在obj上添加的fn这个属性
return result; // 返回结果
}
/* 手写apply函数 */
// apply 与 call 不同点就是第二个参数。call 传递的是参数序列;apply传递的是一个数组
Function.prototype.myApply = function(obj){
if(typeof this != 'function'){
console.error("调用者不是函数")
}
let result = null;
obj = obj || window;
obj.fn = this;
if(arguments[1]){
result = obj.fn(...arguments[1])
}else{
result = obj.fn()
}
return result;
}
// 测试
let dog = {
name: '狗',
eat(food1, food2) {
console.log(this.name + '爱吃' + food1 + food2);
}
}
let cat = {
name: '猫',
}
dog.eat.call(cat, '鱼', '肉'); // 猫爱吃鱼肉
dog.eat.myCall(cat, '鱼', '肉'); // 猫爱吃鱼肉
dog.eat.myApply(cat, ['鱼', '肉']); // 猫爱吃鱼肉
</script>
7、手写bind函数
function Text(a, b, c) {
console.log("this--", this);
console.log(a, b, c)
}
/* 手写bind函数 */
/*
1、改变this指向
2、第一个参数是 this的值,后面的参数是 函数接收的参数的值
3、返回值不变
*/
Function.protoype.myBind = function(){
if(typeof this != "function"){
console.error("调用者不是函数")
}
let self = this; // this指的是函数调用者 Text函数
let arr = [...arguments]; // 使用扩展运算符转为数组
let fn = arr.shift(1); // 获取数组 arr的第一项参数,也就是 {name:"xuxi"}; 此时的arr数组变成了 [1,2,3]
return function(){
return self.apply(fn.arr); // 改变this的指向
}
}
let bind = Text.myBind({ name: 'xuxi' }, 1, 2, 3)
bind(); //bind接受的是一个函数,所以要进行调用
8、封装ajax
/* 封装ajax */
function myAjax(option) {
// 1、创建 XMLHttpRequset对象
let xhr = new XMLHttpRequest();
option = option || {};
// 初始化
option.type = (option.type || 'GET').toUpperCase();
option.dataType = option.dataType || 'json';
let param = option.data; // 只有一个参数
// 2、判断请求类型
if (option.type === 'GET') {
// 3、建立链接
xhr.open(option.type, option.url + '?' + param, true);
// 4、发送请求
xhr.send(null);
} else {
xhr.open(option.type, option.url, true);
xhr.send(param);
}
// 5、更加返回参数做出响应
xhr.onreadystatechange = function () {
let readystate = xhr.readyState;
if (readystate === 4) { // 整个请求过程完毕
let status = xhr.status;
if (status >= 200 && status < 300) { // 200~299 成功的请求
option.success && option.success(xhr.responseText, xhr.responseXML)
}
} else {
option.fail && option.fail("error!!!")
}
}
}
// 调用
myAjax({
type: 'post',
dataType: 'json',
data: {},
url: 'http://XXXX',
success: function (text, xml) {//请求成功后的回调函数
console.log(text)
},
fail: function (status) { //请求失败后的回调函数
console.log(status)
}
})
9、手写深拷贝
/* 手写深拷贝 */
// 第一种 JSON.stringify()
/* var deepCopy = function(obj){
if(!obj || typeof obj != 'object') return;
return JSON.parse(JSON.stringify(obj))
} */
// 第二种方法 _.cloneDeep
/* var _ = require('lodash')
var deepCopy = function(obj){
if(!obj || typeof obj != 'object') return;
return _.cloneDeep(obj)
} */
// 第三种 递归实现
var deepCopy = function(obj){
if(!obj || typeof obj != 'object') return;
let newObj = Array.isArray(obj)? [] :{};
for(let i in obj){
if(obj[i] instanceof Object){
newObj[i] = deepCopy(obj[i]); // 如果对象中的属性也是对象,那么就递归循环
}else{
newObj[i] = obj[i]
}
}
return newObj;
}
let student = {
name: '小明',
age: 10,
grrifriend: {
name: '小麻花'
}
}
let obj = deepCopy(student);
student.grrifriend.name = "小茜";
console.log("student---",student);
console.log("obj---",obj)
</script>
10、实现数组扁平化
/* 第一种方法 递归实现 */
var flatten = function(arr){
let result = [];
for(let i=0; i<arr.length; i++){
if(Array.isArray(arr[i])){
// 如果遇到数组中的元素为数组时,则把该数组元素扁平化,与之前进行连接,并且重新赋值给reslut
result = result.concat(flatten(arr[i]));
}else{
result.push(arr[i]);
}
}
return result;
}
/* 第二种方法 split和toString*/
/* var flatten = function(arr){
return arr.toString().split(",")
} */
/* 第三种方法 ES6中的flat*/
// 语法 arr.flat([depth]) 其中depth是flat的参数,depth是可以传递数组的展开深度(默认不填,数值为1),
// 即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开。
/* var flatten = function(arr){
return arr.flat(Infinity)
}
*/
let arr = [1, [2, [3, 4, 5]]];
console.log(flatten(arr)); // [1,2,3,4,5]