一、书写代码部分
1.实现一个new操作符
function mynew(func, ...args) {
const obj = {}
obj.__proto__ = func.prototype
// apply通常是构造方法上的一个方法
// 执行的目的是将构造方法func的this指向obj,同时将args以参数的形式传入构造方法func中
// 同时用result接收的目的是为了看执行func之后函数有没有返回值
// 可以达到判断这个返回值是原始类型还是对象类型
// 因为new中有固定,执行new关键字的构造函数,若返回值是基础类型,则略过
// 若返回值是对象类型,则实例对象就是这个返回的对象
let result = func.apply(obj, args)
return result instanceof Object ? result : obj
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name)
}
let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui
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是引用类型的数据,那么两者之间数据的改变是互相影响的,因为拷贝的只是其中一个对象的引用地址,于是接下来实现深层拷贝,使得拷贝之后的数据互不影响
function deepCopy (target, hash = new WeakMap()) {
if (typeof target !== 'object' || target === null) return target;
if (hash.has(target)) return hash.get(target)
let copyObj = Array.isArray(target) ? [] : {}
console.log('copyObj',copyObj)
for (const key in target) {
if (target.hasOwnProperty(key)) {
if (typeof target[key] === 'object' && target[key] !== null) {
copyObj[key] = deepCopy(target[key], hash)
} else {
copyObj[key] = target[key]
}
}
}
return copyObj
}
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的博客-优快云博客_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 });