new
原理:
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 返回新对象
实现:
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数,以便实现作用域的绑定
let Con = [].shift.call(arguments)
// 由于通过new操作创建的对象实例内部的不可访问的属性[[Prototype]](有些浏览器里面为__proto__)
// 指向的是构造函数的原型对象的,所以这里实现手动绑定。
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}
其他思路:
function create() {
// 获得构造函数,以便实现作用域的绑定
let Con = [].shift.call(arguments);
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
var newObj = Object.create(Con.prototype);
// 作用域的绑定
Con.apply(newObj, arguments);
return newObj ;
}
instanceof
原理:
instanceof 接收两个参数,第一个为instance(实例),第二个为要Constructor(构造函数),返回值为boolean,函数内的实现其实就是对instance的原型链进行遍历(其实就是一个链表遍历),如果遍历到了某一项原型等于这个Constructor的原型,则返回true
实现:
function myInstanceof(instance, Constructor){
if(typeof instance !== "object" || instance === null){
throw "the instance must be 'Object'";
}
if(typeof Constructor !== "function"){
throw "the Contructor must be 'Function'"
}
let prototype = instance.__proto__;
// 遍历链表用while
while(true){
if (prototype === null){
return false;
}else if(prototype === Constructor.prototype){
return true;
}else{
prototype = prototype.__proto__;
}
}
}
函数防抖(debounce)和 函数节流(throttle)
函数防抖
函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
简单的说,当一个动作连续触发,则只执行最后一次。
应用场景:
连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染
实现:
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行, false - 延迟执行
*/
function debounce(func, wait, immediate) {
var timer; // 维护一个 timer
return function () {
// 因为需要防抖一般是浏览器事件,那么我们就需要确保使用 debounce 处理过的事件处理程序不能出现一些意外的错误
// 比如 this 指向和 event 对象
// 以下两句代码用于解决 this 和 event 不正确的问题
var context = this; // 取debounce执行作用域的this
var args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
// 如果已经执行过,不再执行
var hasCallback = !timer;
timer = setTimeout(function(){
timer = null; // 考虑到不再触发之后的情况,如果不置 timer 为null 就无法再实现下次的立即执行!
}, wait)
if (hasCallback) func.apply(context, args)
}
else {
timer = setTimeout(function(){
func.apply(context, args); // 用apply指向调用debounce的对象,相当于this.func(args);
}, wait);
}
}
}
函数节流
每隔一段时间,只执行一次函数。
应用场景:
间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 谷歌搜索框,搜索联想功能
- 高频点击提交,表单重复提交
原理:
通过使用定时任务,延时方法执行。在延时的时间内,方法若被触发,则直接退出方法。从而,实现函数一段时间内只执行一次。
实现:
function throttle(fn, interval) {
let canRun = true;
return function (...args) {
if (!canRun) return // 如果 interval 时间内再次触发会在此处阻拦
canRun = false;
setTimeout(() => {
canRun = true;
fn.apply(this, args); // this 指向这个节流函数的调用者
}, interval)
}
}
异同比较
相同点:
- 都可以通过使用
setTimeout
实现。 - 目的都是,降低回调执行频率。节省计算资源。
不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
- 函数防抖关注一定时间连续触发,只在最后执行一次,而函数节流侧重于一段时间内只执行一次。
深拷贝deepclone
简单版
function deepClone(obj){
var cloneObj;
//当输入数据为简单数据类型时直接复制
if(obj === null || Object.prototype.toString.call(obj)==="[object RegExp]" || typeof obj !== 'object'){
cloneObj = obj;
}else{
//检测输入数据是数组还是对象
cloneObj = Array.isArray(obj) ? [] : {};
Object.keys(obj).forEach((key)=>{
cloneObj[key] = deepClone(obj[key])
})
}
return cloneObj;
}
改进版
原理:
判断类型是否为原始类型,如果是,无需拷贝,直接返回
为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
对引用类型递归拷贝直到属性为原始类型
实现:
const deepClone = (target, cache = new WeakMap()) => {
if(target === null || typeof target !== 'object') {
return target;
}
if(cache.get(target)) {
return target;
}
const copy = Array.isArray(target) ? [] : {};
cache.set(target, copy);
Object.keys(target).forEach(key => copy[key] = deepClone(target[key], cache));
return copy;
}
尤雨溪版
function find(list, f) {
return list.filter(f)[0]
}
function deepCopy(obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache))
return copy
}
call
原理:
第一个参数为
null
或者undefined
时,this
指向全局对象window
,值为原始值的指向该原始值的自动包装对象,如String
、Number
、Boolean
为了避免函数名与上下文(
context
)的属性发生冲突,使用Symbol
类型作为唯一值将函数作为传入的上下文(
context
)属性执行函数执行完成后删除该属性
返回执行结果
实现:
Function.prototype.myCall = function(context, ...args) {
if(typeof this !== 'function') {
throw new TypeError('not a function!');
}
// 不传入第一个参数,那么默认为 window
context = context || window;
const key = Symbol();
// 给 context 添加一个属性
// eg: getValue.call(a, 'yck', '24') => a.fn = getValue
context[key] = this;
// eg: getValue.call(a, 'yck', '24') => a.fn('yck', '24')
const result = context[key](...args);
delete context[key];
return result;
}
apply
原理:
前部分与
call
一样第二个参数可以不传,但类型必须为数组或者类数组【注:javascript中常见的类数组有
arguments
对象和 DOM方法的返回结果。比如document.getElementsByTagName()
。】
实现:
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw new TypeError('not a function!');
}
context = context || window;
const key = Symbol();
const args = arguments[1];
context[key] = this;
let result;
// 需要判断是否存在第二个参数,如果存在并且是数组类型,就将第二个参数展开
// Array.prototype.slice.call将类数组转换成数组
if(args && Array.isArray(Array.prototype.slice.call(args))) {
result = context[key](...args);
} else {
result = context[key]();
}
delete context[key];
return result;
}
bind
原理:
使用
call / apply
指定this
返回一个绑定函数
当返回的绑定函数作为构造函数被
new
调用,绑定的上下文指向实例对象设置绑定函数的
prototype
为原函数的prototype
实现:
Function.prototype.myBind = function(context, ...args) {
if(typeof this !== 'function') {
throw new TypeError('not a function!');
}
const fn = this
const bindFn = function (...newFnArgs) {
// bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们绑定的this就需要被忽略
fn.call(
this instanceof bindFn ? this : context,
...args, ...newFnArgs
)
}
bindFn.prototype = Object.create(fn.prototype)
return bindFn
}