一、书写代码部分
1.实现一个new操作符
function myNew(func) {
//1.创建一个空的对象
var obj = {};
//2.将新创建的新对象的隐形原型指向构造函数的显示原型
obj.__proto__ = func.prototype;
//由于arguments是类数组,需要转换才能调用数组的方法,需要从第二个开始获取参数
var agrs=[...arguments].slice(1)
//改变this指向并获取执行结果
var res = func.apply(obj, agrs)
return typeof res === 'object' ? res : obj
}
function A(name, age) {
this.name = name;
this.age = age;
}
var obj1 = myNew(A, 'hzz', 18);
//等价于
var obj2=new A('hzz',18);
console.log(obj1)
2.实现一个call或者apply函数:
2.1 call语法
func.call(thisArg,arg1,arg2,...),调用一个函数,其具有一个指定的this指向和列表参数
2.2 call实现
基本思路:
- 1.判断传入的是否为函数对象,不是的话提示错误信息
- 2.判断传入的上下文是否存在,不存在则指向window
- 3.处理传入的参数,即截取除了第一个参数后的所有参数
- 4.将方法绑定到上下文对象的属性上(这是变得指定对象调用的关键)
- 5.获取调用后的结果,并删除刚才新增的属性(context.fn)
//下面是call函数的实现
//注意:不管是call还是apply都必须是函数调用,只是传入call或者apply参数的可以是任意对象
//没有Array.call
Function.prototype.myCall = function (context) {
var _this = this;
var res = null; //用于存储结果
//1.判断是否为函数调用
if (typeof _this !== 'function') {
throw new TypeError('出错了')
}
//2.判断传入的对象是否存在
context = context || window
context.fn = this;
//3.获取参数
var agrs = [...arguments].slice(1)
//调用的函数添加为传入对象的一个属性
res = context.fn(...args)
//删除新增的属性
delete context.fn;
//返回结果
return res
}
//*******下面是测试代码***********
var obj1 = {
name: 'hzz',
age: 18,
sayName: function (xx) {
console.log(this.name)
}
}
var obj2 = {
name: 'xxx'
}
obj1.sayName.myCall(obj2, 'ss')//xxx,这里的'ss'是无用参数
var arr1 = [1, 2, 3, 4]
var arr2 = [4, 8, 9, 12]
const arr3 = arr1.filter.myCall(arr2, function (item) {
return item > 2
})
console.log(arr3)// [4, 8, 9, 12]
2.4 apply语法
func.call(thisArg,[argsArray]),调用一个函数,其具有一个指定的this指向和数组参数(或类似数组对象)
2.5apply实现
基本思路:与call一样,只是处理参数方面有所差异
Function.prototype.myApply = function (context) {
var res = null;
if (typeof this !== 'function') {
throw new TypeError('error')
}
context = context || window;
context.fn = this;
var args = [...arguments].slice(1);
//处理参数不一样,即判断当有参数的时候传入参数调用,没有参数的时候直接调用
if (arguments[1]) {
res = context.fn(...arguments[1])
} else {
res = context.fn()
}
delete context.fn
return res
}
//********测试代码************
const arr4 = arr2.filter.myApply(arr1, [function (item) {
return item > 2
}])
console.log(arr4)
2.6 升级简约版的call、apply的实现
Function.prototype.myCall = function (context, ...args) {
context = context || window;
//创造唯一值,作为我们构造的context内部的方法名
let fn = Symbol();
//this指向调用call的函数
context[fn] = this;
//执行函数 并返回结果(相当于把自身作为传入context的方法进行调用了
return context[fn](...args)
}
Function.prototype.myApply = function (context, agrs) {
context = context || window;
var fn = Symbol();
context[fn] = this;
return context[fn](...agrs)
}
3.bind函数的实现(待)
4.实现instanceof
function myInstanceof(left, right) {
while (true) {
if (left === null) {
return false
}
if (left.__proto__ === right.prototype) {
return true
}
//循环的条件
left = left.__proto__
}
}
5.数组去重
//方法1 使用set
function uniqueArr(arrr{
return [...new Set(arr)];
}
//方法二 使用filter
function uniqueArr(arr){
return arr.filter((item,index)=>{
return arr.indexOf(item)===index;
}
}
6.数组扁平化
//直接使用flat函数,其中默认降2维,可以传入无限大数据使得不管多少维数组全部降为一维数组
const res1=arr.flat(Infinity)
7.实现一个你认为不错的js继承方式
这里实现的是寄生虫组合继承,思路即是两个构造函数之前的继承
实现代码如下:
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name)
}
}
Person.prototype.running = function () {
console.log('runnig')
}
Person.prototype.eating = function () {
console.log('eating')
}
function Student(name, age, sno, friends) {
Person.call(this, name, age);
this.sno = sno;
this.friends = friends;
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student//细分构造函数的类型
console.log(Student.prototype)
let obj = new Student('hzz', 18, '171010204', 'lhk')
console.log(obj)
obj.running()
8.防抖与节流
由于很早之前就有看过防抖节流的原理,源于当时看的时候刚接触前端,所以也是一知半解,更是分不清t防抖与节流的异同之处,今天在这里重新梳理一下其中的原理,并作于区分
8.1 防抖
1.理解
防抖即是当你触发事件的时候,其对应的函数不会立即触发,而是被推迟一定的时间后触发;比如有个input输入框,我想输入"abc d"进行搜索的时候,如果你不做防抖处理,那么你每一此输入a、b、...都会触发事件,当你做了防抖处理的时候,①那么等你输入abc之后,间隔了一定的时间你没有再次输入,那么就会在这时触发事件,而假设你在输入abc之后,你停留了一定的时间,再次输入d的时候,当时停留的时间<你设置的时间,那么这个时间在你再次输入便会重新计算,然后重复①这样下去,就是防抖;
2.防抖的好处
减少向服务端发送请求的次数,tigao性能
3.防抖的应用
- input框输入
- 点击提交事件的按钮
- 屏幕的滚动
4.防抖的原理实现
function debounce(fn, waitTime) {
let timer = null;
//返回这个函数,倒是触发事件的时候才触发这个函数
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
//由于这个fn是传递进来的函数,且是独立调用的,因此
//因此this指向并不是指向我触发的对象
//且我们触发对象触发的函数实质是返回的函数,因此this指向我们可以从返回中的函数中获取
//这里是箭头函数,所以可以直接向上查找
fn.apply(this, args)
}, waitTime)
}
}
//下面是测试
let count = 0
const inputChange = function (event) {
console.log(`触发了${++count}事件`, this, event)
}
//获取input原始
let item = document.querySelector("input")
//这里是没有做防抖的是,触发事件回立即执行inputChange函数
// item.oninput = inputChange
//这里是做了防抖处理
item.oninput = debounce(inputChange, 1000)
5.上截图方便日后回头看便于理解
8.2 节流
节流函数即是控制一定时间内时间触发的频率,不管触发多少次,这个频率是固定的
应用:
function thortter(fn, await) {
//leading表示最后
//记录上一次的开始时间,将lastTime放在外面的原因是,这个lastTime是变化的,我们需要记录其值
//如果放在函数内部的话,那么每次触发事件函数的时候,这个lastTime都是新的
let lastTime = 0;
const _throtter = function (...args) {
//每次触发函数的时候都可以得到最新的时间
const nowTime = new Date().getTime()
//当lastTime时间与当前时间差大于或者等于设置的频率时间的时候,就触发函数
if (nowTime - lastTime >= await) {
fn.apply(this, args)
//更新改变
lastTime = nowTime;
}
}
return _throtter;
}
9.实现深拷贝
我们之前说的拷贝函数就是常规只能拷贝第一层,当拷贝的数据是引用类型的时候,拷贝的只是对象的引用,比如a={},b={}之间进行拷贝,那么由于a、b是引用类型的数据,那么两者之间数据的改变是互相影响的,因为拷贝的只是其中一个对象的引用地址,于是接下来实现深层拷贝,使得拷贝之后的数据互不影响
①初级版本,没有考虑拷贝到时候假设对象属性指向自身的情况
下面自定义一个深拷贝函数,主要实现功能如下:
- 若传入的类型是Symbol,直接一个返回新创建的New Symbol(originalValue.description)
- 若传入的是函数类型,直接返回返回(如果想新复制一个新的函数是很复杂的,且函数的作用就是想实现复用性,因此这里我们直接返回该函数即可)
- 若传入的是Set或者Map类型的数据,那么新创建返回:new Set([...originalValue])、new Map([...originalValue]
- 若传入的值不是我们使用isObject(obj)判断的对象类型,则说明是原始值,直接return 返回
- 接下来是对传入值为对象的时候进行遍历递归克隆,但是注意的是当属性是数组的时候,需要特殊处理:const newObject = Array.isArray(originalValue) ? [] : {}
- 进入最后的遍历递归调用的说明此时的属性属于对象,使用newObject[key] = deepClone(originalValue[key])
- 最后return新创建的对象newObject
//判断是否为对象
function isObject(obj) {
return typeof obj !== "null" && (typeof obj === "object" || typeof obj === "function")
}
function deepClone(originalValue) {
//0.由于默认情况下是不对Symbol数据类型进行拷贝的
//如果是则新穿件一个Symbol
if (typeof originalValue === "symbol") {
return Symbol(originalValue.description)
}
//1.当不是对象数据类型的时候,直接返回
if (!isObject(originalValue)) {
return originalValue
}
//2.对属性为函数的时候进行处理
if (typeof originalValue === 'function') {
return originalValue
}
//判断是否为set数据类型
if (originalValue instanceof Set) {
return new Set([...originalValue])
}
//判断是否为map类型
if (originalValue instanceof Map) {
return new Map([...originalValue])
}
//3.这里主要是对数组的处理,当被克隆的对象的属性是数组的时候,返回的应当是数组类型
const newObject = Array.isArray(originalValue) ? [] : {}
for (const key in originalValue) {
//进入这里说明传入的值是一个对象的数据,因此这里遍历这个对象
newObject[key] = deepClone(originalValue[key])
}
return newObject
}
//测试代码
let s1 = Symbol('a')
const obj = {
name: 'hzz',
age: 18,
sym: s1,
arr1: [1, 2, 2, 3],
friends: {
name: 'wzj',
age: 22
},
sayName: function () {
console.log(this.name)
},
set1: new Set(['aaa', 'bbb', 'ccc']),
map1: new Map([["aaa", "bbb"], ["vvv", "bbbb"]])
}
var newObj = deepClone(obj)
obj.friends.name = "xxxx"
console.log(newObj.friends.name)
console.log(newObj)
newObj.sayName()
②升级版本,考虑对象属性指向自己的情况
//判断是否为对象
function isObject(obj) {
return typeof obj !== "null" && (typeof obj === "object" || typeof obj === "function")
}
function deepClone(originalValue, map = new Map()) {
//0.由于默认情况下是不对Symbol数据类型进行拷贝的
//如果是则新穿件一个Symbol
if (typeof originalValue === "symbol") {
return Symbol(originalValue.description)
}
//1.当不是对象数据类型的时候,直接返回
if (!isObject(originalValue)) {
return originalValue
}
//2.对属性为函数的时候进行处理
if (typeof originalValue === 'function') {
return originalValue
}
//判断是否为set数据类型
if (originalValue instanceof Set) {
return new Set([...originalValue])
}
//判断是否为map类型
if (originalValue instanceof Map) {
return new Map([...originalValue])
}
//表示当有的时候直接返回,防止无线循环
if (map.has(originalValue)) {
return map.get(originalValue)
}
//3.这里主要是对数组的处理,当被克隆的对象的属性是数组的时候,返回的应当是数组类型
const newObject = Array.isArray(originalValue) ? [] : {}
//设置指向自己的循环
map.set(originalValue, newObject)
for (const key in originalValue) {
//进入这里说明传入的值是一个对象的数据,因此这里遍历这个对象
newObject[key] = deepClone(originalValue[key], map)
}
return newObject
}
//测试代码中加入
obj.info = obj
10.promise是什么?能否手写一个?
Promise是异步编程的一种解决方案
一个Promise有以下几种状态:
- pending:初始等待状态,既不是成功,也不是失败的状态
- fulfilled:以为这操作成完成
- reject:以为这操作失败
而且需要注意的是,Promise的等待状态一旦改变,则不会再次发生改变,也就是说一旦变成fulfilled或者rejected状态,就不能再次改变。pending的状态一旦改变,Promise对象的then方法就会被调用,否则就会触发catch
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1);
resolve()
}, 100)
}).then(res => {
setTimeout(() => {
console.log(2);
}, 2000)
}).then(res => {
setTimeout(() => {
console.log(3)
}, 3000)
}).catch((err) => {
console.log(err)
})
以下是Promise的手写实现,面试够用!
//Promise的手写实现
function muPromise(constructor) {
let _this = this;
//1.定义状态改变前的初始状态
_this.status = 'pending';
//2.分别订货状态为resolved和rejected的时候的状态
_this.value = undefined;
_this.reason = undefined;
function resolve(value) {
//使用“===”,保证了状态改变是不可逆的
//这里表示不管你执行了resolve还是reject回调函数的时候,其其实的状态只能是‘pending’
//所以需要提前判断,所以进入回调函数之后,改变其状态
if (_this.status === 'pending') {
_this.value = value;
_this.status = 'resolved';
}
}
function reject(reason) {
if (_this.status === 'pending') {
_this.reason = reason;
_this.status = "rejected"
}
}
//捕获构造函数异常
try {
//将resolve,reject作为回调函数的参数,这个回调函数又是Promise构造函数的参数
//类似new Promise((resolve,reject)=>{})
constructor(resolve, reject);
} catch (e) {
console.log(e)
}
}
//定义链式调用then方法
myPromise.prototype.then = function (onFullfilled, onRejected) {
//传入的onFulfilled、onRejected均是回调函数
let _this = this;
switch (_this.status) {
case 'resolved':
//即将resolve(value)中的参数回调到onFullfilled回调函数中
onFullfilled(_this.value);
break;
case "rejected":
//即将resolve(value)中的参数回调到onRejected回调函数中
onRejected(_this.reason);
break;
default:
}
}
11.手写Promise.all
一般来数,Promise.all是用来处理多个并发请求,在一个页面所用到的不同接口的数据一起请求过来,不过在Promise.all中,如果其中有一个接口失败,那么多个请求也就失败了,页面可能啥也不来,这就看当前页面的耦合程度了。
实现思路:
- 接受一个Promise实例的数组或者具有Iterator接口的对象作为参数
function promisAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promisesNum = promises.length
var resolveResult = []//存储所有返回值结果
for (let i = 0; i < promisesNum; i++) {
Promise.resolve(promises[i]).then(value => {
resolvedCounter++;
resolveResult[i] = value
if (resolvedCounter === promises.length) {
return resolve(resolveResult)
}
}, error => {
return reject(error)
})
}
})
}
let p1 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(3)
}, 3000)
})
promisAll([p1, p2, p3]).then(res => {
console.log(res)
})
12.实现Promise.race
13.实现bind函数
14.实现Ajax请求
可参考:Ajax请求的五个步骤_weixin_45846357的博客-CSDN博客_ajax请求的五个步骤
ajax是异步请求的一种方式,同时ajax异步请求也不会刷新整个页面,也是局部更新
船舰ajax请求的步骤:
- 新创建一个XMLHttpRequest异步对象
var xhr = new XMLHttpRequest();
2.在这个对象上使用open方法创建一个HTTP请求,open方法所需要的参数是请求方式、请求的地址、是否异步和用户的认证信息
// get请求如果有参数就需要在url后面拼接参数,
// post如果有参数,就在请求体中传递 xhr.open("get","validate.php?username="+name)
xhr.open("post","validate.php");
3.设置请求体send()
// 1.get的参数在url拼接了,所以不需要在这个函数中设置
// 2.post的参数在这个函数中设置(如果有参数)
xhr.send(null) xhr.send("username="+name);
4.让异步对象接收服务器的响应数据,一个成功的响应有两个条件:
- 服务器成功响应了
- 异步对象的响应状态为4(数据解析完毕可以使用了)
xhr.onreadystatechange = function(){
if(xhr.status == 200 && xhr.readyState == 4){
console.log(xhr.responseText);
}
具体实现如下:
const baseURL = '/server';
let xhr = new XHRHttpRequest();
xhr.open('get', baseURL, true);
xhr.send(null)
xhr.onreadystatechange = function () {
if (this.status === 200 && this.readystate === 4) {
console.log(this.response)
} else {
console.log(this.statusText)
}
}
15.使用promise封装ajax
//使用promise封装ajax请求
function getJson(url) {
return new Promise((resolve, reject) => {
let xhr = new XHRHttpRequest();
xhr.open('get', url, true);
xhr.send(null);
xhr.onreadystatechange = function () {
if (this.status === 200 && this.readystate === 4) {
resolve(this.response);
} else {
reject(this.statusText)
}
}
})
}
16.实现浅拷贝
浅拷贝指的是一个新的对象对原始对象属性值进行精准的拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用类型,那么拷贝的就是引用类型的引用地址,如果其中一个对象的引用地址发生改变,那么另一个对象也会发生变化
实现浅拷贝的方式有如下:
1.Object.assign()
2.扩展运算符:{...obj}
3.数组方法实现数组浅拷贝.slice(),这个方法可以从数组中返回选定的元素,用法:array.slice(start,end),该方法不会改变原数组,两个参数可选,都不写的时候可以实现一个数组的拷贝var copyArr=[1,2,3].slice()
17.手写Object.create
先来看看其作用:
const f = myCreate(obj)
console.log(f.name)
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person); // me.__proto__ === person
me.name = "Matthew"; // name属性被设置在新对象me上,而不是现有对象person上
me.isHuman = true; // 继承的属性可以被重写
me.printIntroduction(); // My name is Matthew. Am I human? true
思路:将传入的对象做为原型
function myCreate(obj) {
function F() { }
F.prototype = obj;
return new F()
}
二、情景反映部分
1.用promise实现图片的异步加载
let imageAsync = (url) => {
return new Promise((resolve, reject) => {
let img = new Image();
img.src = url;
img.onload = () => {
console.log('图片请求成功,此处理进行通用操作')
resolve(image)
}
img.onerror = (err) => {
console.log('失败,处处进行失败的通用操作')
reject(err)
}
})
}
imageAsync("url").then(() => {
console.log("加载成功")
}, () => {
console.log("加载失败")
})
2.实现双向数据绑定
let obj = {}
let input = document.querySelector('input')
let span = document.querySelector("span")
//数据劫持
Object.defineProperty(obj, "text", {
configurable: true,
enumerable: true,
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal;
span.innerHTML = newVal;
}
})
//输入监听
input.addEventListener('keyup', function (e) {
obj.text = e.target.value
})
3.使用setTimeout实现setInterval
setInterval的作用是每隔一段时间执行一个函数,但是这个执行不是真的到了时间会立即执行,他的真正作用是每隔一段时间将时间加入时间队列中去,只有当当前执行栈为空的时候,才去从时间队列中取出时间执行,所有可能会出现这样的情况,就是当执行长执行的时间很长时,导致事件队列里面积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能带间隔一段时间执行的效果
针对setInterval的这个缺点,我们可以使用setTimeout递归调用来模拟setInterval,这样我们就的确保了只有一个事件结束了,我们就会触发下一个定时器,便解决了setInterval的问题
实现思路时使用递归函数,不断去执行setTimeout从而达到setInterval效果
function mySetInterval(fn, timeout) {
// 控制器,控制定时器是否继续执行
var timer = {
flag: true
};
// 设置递归函数,模拟定时器执行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
4.实现jsonp
function addScript(scr, callBack) {
const script = document.createElement('script');
script.src = scr;
script.type = "text/javascript";
document.appendChild(script)
}
function handleRes(res) {
console.log(res)
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 接口返回的数据格式
handleRes({ a: 1, b: 2 });