概述
1.new的实现
new Foo(...)
new一个对象就是创建一个新对象,这个新对象既拥有Foo的constructor里的属性,又继承了Foo.prototype,并返回这个新对象。
步骤如下:
1、创建一个空对象;
2、让空对象的__proto__(IE没有该属性)成员指向了构造函数的prototype成员对象
3、使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
4、如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象
function _new() {
const obj= {}; // 创建的新对象
// arguments伪数组的第一个参数就是构造函数,因此用shift取出第一个参数,[]等价于Array.prototype
const constructor = [].shift.call(arguments);
const args = [...arguments].slice(1); //获取argument除了第一个以外的参数
// 执行 [[原型]] 连接 ;实际上就是生产了一个新的上下文
obj.__proto__ = constructor.prototype;
// 使用apply在obj作用域中调用构造器函数,属性和方法被添加到 this 引用的对象即obj中
const result = constructor.apply(obj, args);
//如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象
return typeof result === 'object' ? result : obj;
}
2.call、apply、bind的实现。
2.1 call的实现
完整代码如下:
Function.prototype._call = function(thisArg) {
if (typeofthis !== 'function') { // this指向调用call的对象
thrownewTypeError('Error'); // 调用call的若不是函数则报错
}
const args = [...arguments].slice(1); //获取argument除了第一个以外的参数
thisArg = thisArg || window; //考虑当thisArg为null的情况,此时实际_call接受的参数是window
// 将调用call函数的对象添加到thisArg的属性中
//这里的this指的是调用_call()的对象,而fn是临时起的中间变量(名字随意),临时使用后在后面会删除
thisArg.fn = this;
// 执行该属性
const result = thisArg.fn(...arg);
// 删除该属性
delete thisArg.fn;
// 返回函数执行结果
return result;
};
2.2 apply的实现
apply的实现与call类似:
Function.prototype.myapply = function(thisArg) {
if (typeofthis !== 'function') {
thrownewTypeError('Error');
}
const args = arguments[1]; //获取arguments的第二个参数,即apply获取的数组参数
thisArg.fn = this;
const result = thisArg.fn(...arg);
delete thisArg.fn;
return result;
};
2.3 bind的实现
Function.prototype._bind = function () {
// 思路:bind函数实际上每次返回的是一个函数
// bind 函数的第一个参数是this的指向,其他参数是传递的参数
// 1. 接受参数,解析this和args
var self = this; //原函数
var context = [].shift.call(arguments); //arguments首元素,即_bind()括号里绑定的函数
var args = [].slice.call(arguments, 1); //接受参数,获取arguments剩余参数转成数组
return function(){ // 处理用户传递的参数,arguments 就是用户传递给我的函数参数
// 修改this的指向, 用concat方法接收多个参数组成的数组
self.apply(context, args.concat([].slice.call(arguments)))
}
}
call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
举个例子:
var foo = { value: 1 }; function bar() { console.log(this.value); } bar.call(foo); // 1
注意两点:
- 1.call 改变了 this 的指向,指向到 foo
- 2.bar 函数执行了
我们该怎么模拟实现这两个效果呢?
试想当调用 call 的时候,把 foo 对象改造成如下:
var foo = { value: 1, bar: function() { console.log(this.value) } }; foo.bar(); // 1
这个时候 this 就指向了 foo,是不是很简单呢?
但是这样却给 foo 对象本身添加了一个属性,这可不行呐!
不过也不用担心,我们用 delete 再删除它不就好了~
所以我们模拟的步骤可以分为:
- 将函数设为对象的属性
- 执行该函数
- 删除该函数
以上个例子为例,就是:
第一步 foo.fn = bar 第二步 foo.fn() 第三步 delete foo.fn
fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。
3. Ajax的实现
1、原生ajax的实现
function ajax(method, url, async) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
}else if(xhr.status >= 400) {
console.log("请求失败")
}
}
}
xhr.open(method, url, async);
xhr.send(null);
}
2、满足promise的jQuery.ajax的实现:
window.jQuery.ajax = function({url, method}) { // 传参是 es6解构赋值
return new Promise ( function(resolve, reject){ // 这一行很关键
let xhr = new XMLHttpRequest()
xhr.open(method, url)
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve.call(undefined, xhr.responseText)
} else if (xhr.status >= 400) {
reject.call(undefined, xhr)
}
}
}
xhr.send()
})
}
4. promise的实现
https://www.jianshu.com/p/c633a22f9e8c
4.1 Promise代码基本结构
实例化Promise对象时传入一个函数作为执行器,有两个参数(resolve和reject)分别将结果变为成功态和失败态。当实例化Promise时,构造函数中就要马上调用传入的executor函数执行。我们可以写出基本结构:
function Promise(executor) {
var _this = this;
this.state = 'pending'; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
executor(resolve, reject); //马上执行
function resolve(value) { }
function reject(reason) { }
}
module.exports = Promise;
其中state属性保存了Promise对象的状态,规范中指明,一个Promise对象只有三种状态:等待态(pending)成功态(resolved)和失败态(rejected)。
当一个Promise对象执行成功了要有一个结果,它使用value属性保存;也有可能由于某种原因失败了,这个失败原因放在reason属性中保存。
已经是成功态或是失败态不可再更新状态:当Promise对象已经由pending状态改变为了成功态(resolved)或是失败态(rejected)就不能再次更改状态了。因此我们在更新状态时要判断,如果当前状态是pending(等待态)才可更新:
function resolve(value) {
//当状态为pending时再做更新
if (_this.state === 'pending') {
_this.value = value;//保存成功结果
_this.state = 'resolved';
}
}
function reject(reason) {
//当状态为pending时再做更新
if (_this.state === 'pending') {
_this.reason = reason;//保存失败原因
_this.state = 'rejected';
}
}
then方法:每一个Promise实例都有一个then方法,它用来处理异步返回的结果,它是定义在原型上的方法:
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.state === 'resolved') {
//判断参数类型,是函数执行之
if (typeof onFulfilled === 'function') {
onFulfilled(this.value);
}
}
if (this.state === 'rejected') {
if (typeof onRejected === 'function') {
onRejected(this.reason);
}
}
};
让promise支持异步:
我们可以参照发布订阅模式,在执行then方法时如果还在等待态(pending),就把回调函数临时寄存到一个数组里,当状态发生改变时依次从数组中取出执行就好了,清楚这个思路我们实现它,首先在类上新增两个Array类型的数组,用于存放回调函数:
function Promise(executor) {
var _this = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledFunc = [];//保存成功回调
this.onRejectedFunc = [];//保存失败回调
//其它代码略...
}
这样当then方法执行时,若状态还在等待态(pending),将回调函数依次放入数组中:
Promise.prototype.then = function (onFulfilled, onRejected) {
//等待态,此时异步代码还没有走完
if (this.state === 'pending') {
if (typeof onFulfilled === 'function') {
this.onFulfilledFunc.push(onFulfilled);//保存回调
}
if (typeof onRejected === 'function') {
this.onRejectedFunc.push(onRejected);//保存回调
}
}
//其它代码略...
};
寄存好了回调,接下来就是当状态改变时执行就好了:
function resolve(value) {
if (_this.state === 'pending') {
_this.value = value;
//依次执行成功回调
_this.onFulfilledFunc.forEach(fn => fn(value));
_this.state = 'resolved';
}
}
function reject(reason) {
if (_this.state === 'pending') {
_this.reason = reason;
//依次执行失败回调
_this.onRejectedFunc.forEach(fn => fn(reason));
_this.state = 'rejected';
}
}
使promise支持链式调用:
/** * 解析then返回值与新Promise对象
* @param {Object} promise2 新的Promise对象
* @param {*} x 上一个then的返回值
* @param {Function} resolve promise2的resolve
* @param {Function} reject promise2的reject
*/
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Promise发生了循环引用'));
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//可能是个对象或是函数
try {
let then = x.then;
if (typeof then === 'function') {
let y = then.call(x, (y) => {
//递归调用,传入y若是Promise对象,继续循环
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} else {
//是个普通值,最终结束递归
resolve(x);
}
完整代码如下:
function Promise(executor) {
let self = this
this.status = 'pending' //当前状态
this.value = undefined //存储成功的值
this.reason = undefined //存储失败的原因
this.onResolvedCallbacks = []//存储成功的回调
this.onRejectedCallbacks = []//存储失败的回调
function resolve(value) {
if (self.status == 'pending') {
self.status = 'resolved'
self.value = value
self.onResolvedCallbacks.forEach(fn => fn());
}
}
function reject(error) {
if (self.status == 'pending') {
self.status = 'rejected'
self.reason = error
self.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
Promise.prototype.then = function (infulfilled, inrejected) {
let self = this
let promise2
infulfilled = typeof infulfilled === 'function' ? infulfilled : function (val) {
return val
}
inrejected = typeof inrejected === 'function' ? inrejected : function (err) {
throw err
}
if (this.status == 'resolved') {
promise2 = new Promise(function (resolve, reject) {
//x可能是一个promise,也可能是个普通值
setTimeout(function () {
try {
let x = infulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
})
}
if (this.status == 'rejected') {
promise2 = new Promise(function (resolve, reject) {
//x可能是一个promise,也可能是个普通值
setTimeout(function () {
try {
let x = inrejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
})
}
if (this.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function () {
//x可能是一个promise,也可能是个普通值
setTimeout(function () {
try {
let x = infulfilled(self.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
})
self.onRejectedCallbacks.push(function () {
//x可能是一个promise,也可能是个普通值
setTimeout(function () {
try {
let x = inrejected(self.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
})
})
}
return promise2
}
//处理链式调用问题
function resolvePromise(p2, x, resolve, reject) {
if (p2 === x && x != undefined) {
reject(new TypeError('类型错误'))
}
//可能是promise,看下对象中是否有then方法,如果有~那就是个promise
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {//为了防止出现 {then:11}这种情况,需要判断then是不是一个函数
let then = x.then
if (typeof then === 'function') {
then.call(x, function (y) {
//y 可能还是一个promise,那就再去解析,知道返回一个普通值为止
resolvePromise(p2, y, resolve, reject)
}, function (err) {
reject(err)
})
} else {//如果then不是function 那可能是对象或常量
resolve(x)
}
} catch (e) {
reject(e)
}
} else {//说明是一个普通值
resolve(x)
}
}
5.闭包的应用
创建10个a标签,点击的时候,弹出对应的序号:
<script>
for (var i = 0; i < 10; i++) {
(function (i) {
var a = document.createElement('a');
a.innerHTML = i + '<br>';
document.body.appendChild(a);//继续放置a标签
a.addEventListener('click', function (e) {
e.preventDefault(); //取消默认事件,指a标签
alert(i);
});
})(i);
}
</script>
6.浅拷贝和深拷贝
浅拷贝:
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
浅拷贝方法:Object.assign()、Array.prototype.concat()、Array.prototype.slice()、ES6的展开运算符"..."
赋值 & 浅拷贝 & 深拷贝的区别:
深拷贝:
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
深拷贝简单做法:JSON.parse(JSON.stringfy(obj)) 但是该方法也是有局限性的:
- 会忽略undefined
- 会忽略symbol
- 会忽略函数
- 不能解决循环引用的对象 (会报错)
自封装深拷贝思路:
- 使用for-in遍历对象
- 因为for-in会遍历原型链上的属性,所以需要判断属性是否在原型链上,不是原型链才拷贝
- 判断属性值类型是原始类型和引用类型
- 原始类型直接赋值(注意null)
- 引用类型判断是对象还是数组,创建对应的空对象或空数组,递归调用函数,将值赋值进去
function deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]))
}
// 判断如果当前的值是null的话;直接赋值为null
} else if(target===null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if(target.constructor===RegExp){
result = target;
}else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
}
7. 防抖节流实现
节流与防抖的本质:以闭包的形式存在,通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用定时器或时间差来控制事件的触发频率。
1、防抖
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
典型应用:
- 百度搜索框在输入稍有停顿时才更新推荐热词。
- 拖拽
function debounce(handler, delay){
delay = delay || 300;
var timer = null;
return function(){
var _self = this,
_args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
handler.applay(_self, _args);
}, delay);
}
}
2、节流
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率,好像水滴积攒到一定程度才会触发一次下落一样。
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。
典型应用:
- 抢券时疯狂点击,既要限制次数,又要保证先点先发出请求
- 窗口调整
- 页面滚动
//定时器法:
function throttle(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
//时间戳版
function throttle(handler, wait) {
wait = wait || 300;
var lastTime = 0;
return function(){
var _self = this,
_args = arguments;
var nowTime = new Date().getTime();
if((nowTime - lastTime) > wait){
handler.apply(_self, _args);
lastTime = nowTime;
}
}
}
8. 观察者模式
观察者模式:观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。而js中最常见的观察者模式就是事件触发机制。
add:添加事件某种类型的函数,
remove: 移除某种类型的函数,
fire:触发某种类型的函数,
once:触发某种类型的函数,然后移除掉这个函数
//ES6实现
class eventObs {
//handler:触发函数,详情如下(其中type表示触发事件类型,handle表示触发后执行的函数):
//handler={
// type1:[func1,func2...],
// type2:[func2,func4...],
// ...
//}
constructor(){
this.handleFunc={}
}
//add:添加一个事件监听,传入参数应该是事件类型type和触发函数func,传入的时候检测有没有这个函数,有了就不重复添加。
add(type,func){
if(this.handleFunc[type]){
if(this.handleFunc[type].indexOf(func)===-1){
this.handleFunc[type].push(func);
}
}else{
this.handleFunc[type]=[func];
}
};
// fire:触发某种类型的函数,触发一个点击事件肯定是要触发它全部的函数,这里也是一样,所以只需要传入type,
//特殊处理:事件可能不存在,像下面remove一样处理。
fire(type,func){
try{
if(arguments.length===1) {
let target = this.handleFunc[type];
let count = target.length;
for (var i = 0; i < count; i++) {
target[i]();
}
}else{
let target = this.handleFunc[type];
let index=target.indexOf(func);
if(index===-1)throw error;
func();
}
return true;
}catch (e){
console.error('别老想搞什么飞机,触发我有的东西!');
return false;
}
};
//remove:移除某种类型的函数,该函数有个潜在需求,就是如果你的事件不存在,它应该会报错。
//而这里不会报错,index在func不存在的时候是-1;这时候要报错。
remove(type,func){
try{
let target = this.handleFunc[type];
let index=target.indexOf(func);
if(index===-1)throw error;
target.splice(index,1);
}catch (e){
console.error('别老想搞什么飞机,删除我有的东西!');
}
};
//fire,然后remove?
//但会有问题,我只想触发并且删除某个事件怎么办,fire一下就全触发了呀。
//所以fire的问题就显现出来了。我们还是要给它一个func,但是可选,即once函数。
once(type,func) {
this.fire(type, func)?
this.remove(type, func):null;
}
}
9. DOM树遍历
// 深度遍历
function interator(node) {
console.log(node);
if (node.children.length) { //该节点有后代,则遍历该节点后代所有节点
for (var i = 0; i < node.children.length; i++) {
interator(node.children[i]);
}
}
}
// 广度遍历
function interator(node) {
var arr = [];
arr.push(node); //存入根节点
while (arr.length > 0) {
node = arr.shift(); //弹出arr中的首个节点
console.log(node);
if (node.children.length) { //该层节点遍历完了,则遍历下一层子节点
for (var i = 0; i < node.children.length; i++) {
arr.push(node.children[i]);
}
}
}
}
10. 数据类型判断
/**
* 类型判断
*/
function getType(target) {
//先处理最特殊的Null
if(target === null) {
return 'null';
}
const typeOfT = typeof target;
//判断是不是基础类型
if(typeOfT !== 'object') {
return typeOfT;
}
//肯定是引用类型了
const template = {
"[object Object]": "object",
"[object Array]" : "array",
"[object Function]": "function",
// 一些包装类型
"[object String]": "object - string",
"[object Number]": "object - number",
"[object Boolean]": "object - boolean"
};
const typeStr = Object.prototype.toString.call(target);
return template[typeStr];
}
最后
以上就是干净睫毛为你收集整理的JS常见方法实现的全部内容,希望文章能够帮你解决JS常见方法实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复