目录
- function deepClone(startObj,endObj) {
- let obj = endObj || {}
- for (let i in startObj) {
- if (typeof startObj[i] === 'object') {
- startObj[i].constructor === Array ? obj[i] = [] : obj[i] = {}
- deepClone(startObj[i],obj[i])
- } else {
- obj[i] = startObj[i]
- }
- }
- return obj
- }
值得注意的一点是,在递归调用的时候,需要把当前处理的 obj[i] 给传回去,否则的话 每次递归obj都会被赋值为空对象,就会对已经克隆好的数据产生影响。
我们验证一下深拷贝是否实现:
- const person = {
- name: 'zyj',
- age: 20,
- sister: {
- name: 'duoduo',
- age: 13,
- mother: {
- name: 'lili',
- age:45
- }
- }
- }
- const newPerson = deepClone(person)
- newPerson.sister.mother.age = 50
- console.log(newPerson)
- // {
- // name: 'zyj',
- // age: 20,
- // sister: { name: 'duoduo', age: 13, mother: { name: 'lili', age: 50 } }
- // }
- console.log(person)
- // {
- // name: 'zyj',
- // age: 20,
- // sister: { name: 'duoduo', age: 13, mother: { name: 'lili', age: 45 } }
- // }
单位时间内,频繁触发一个事件,以最后一次触发为准。
- function debounce(fn,delay) {
- let timer = null
- return function() {
- clearTimeout(timer)
- timer = setTimeout(() => {
- fn.call(this)
- }, delay);
- }
- }
我们看一下调用流程:
- <body>
- <input type="text">
- <script>
- const input = document.querySelector('input')
- input.addEventListener('input',debounce(function() {
- console.log(111);
- },1000))
- function debounce(fn,delay) {
- let timer = null
- return function() {
- clearTimeout(timer)
- timer = setTimeout(() => {
- fn.call(this)
- }, delay);
- }
- }
- script>
- body>
可能有些同学对 fn.call(this) 不太明白,在 debounce 中我们把匿名函数作为参数传进来,因为匿名函数的执行环境具有全局性,所以它的 this 一般指向 window ,所以要改变一下 this 指向,让它指向调用者 input 。
单位时间内,频繁触发一个事件,只会触发一次。
- function throttle(fn,delay) {
- return function () {
- if (fn.t) return;//每次触发事件时,如果当前有等待执行的延时函数,则直接return
- fn.t = setTimeout(() => {
- fn.call(this);//确保执行函数中this指向事件源,而不是window
- fn.t = null//执行完后设置 fn.t 为空,这样就能再次开启新的定时器
- }, delay);
- };
- }
调用流程:
- //节流throttle代码:
- function throttle(fn,delay) {
- return function () {
- if (fn.t) return;//每次触发事件时,如果当前有等待执行的延时函数,则直接return
- fn.t = setTimeout(() => {
- fn.call(this);//确保执行函数中this指向事件源,而不是window
- fn.t = null//执行完后设置 fn.t 为空,这样就能再次开启新的定时器
- }, delay);
- };
- }
- window.addEventListener('resize', throttle(function() {
- console.log(11);
- },1000));
-
只有当调整浏览器视口大小时才会输出,且每隔一秒输出一次
- // 模拟 instanceof
- function myInstance(L, R) {
- //L 表示左表达式,R 表示右表达式
- let RP = R.prototype; // 取 R 的显示原型
- let LP = L.__proto__; // 取 L 的隐式原型
- while (true) {
- if (LP === null) return false;
- if (RP === LP)
- // 这里重点:当 O 严格等于 L 时,返回 true
- return true;
- LP = LP.__proto__;
- }
- }
- function person(name) {
- this.name = name
- }
- const zyj = new person('库里')
-
- console.log(myInstance(zyj,person)); // true
- function getType(obj){
- let type = typeof obj;
- if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
- return type;
- }
- // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
- return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); // 注意正则中间有个空格
- }
- Function.prototype.myCall = function (context) {
- // 先判断调用myCall是不是一个函数
- // 这里的this就是调用myCall的
- if (typeof this !== 'function') {
- throw new TypeError("Not a Function")
- }
-
- // 不传参数默认为window
- context = context || window
-
- // 保存this
- context.fn = this
-
- // 保存参数
- let args = Array.from(arguments).slice(1)
- //Array.from 把伪数组对象转为数组,然后调用 slice 方法,去掉第一个参数
-
- // 调用函数
- let result = context.fn(...args)
-
- delete context.fn
-
- return result
-
- }
- Function.prototype.myApply = function (context) {
- // 判断this是不是函数
- if (typeof this !== "function") {
- throw new TypeError("Not a Function")
- }
-
- let result
-
- // 默认是window
- context = context || window
-
- // 保存this
- context.fn = this
-
- // 是否传参
- if (arguments[1]) {
- result = context.fn(...arguments[1])
- } else {
- result = context.fn()
- }
- delete context.fn
-
- return result
- }
-
在实现手写bind方法的过程中,看了许多篇文章,答案给的都很统一,准确,但是不知其所以然,所以我们就好好剖析一下bind方法的实现过程。
我们先看一下bind函数做了什么:
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
读到这里我们就发现,他和 apply , call 是不是很像,所以这里指定 this 功能,就可以借助 apply 去实现:
- Function.prototype.myBind = function (context) {
- // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
- const self = this;
- // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
- // 这里产生了闭包
- const args = Array.from(arguments).slice(1)
- return function () {
- // 这个时候的 arguments 是指 myBind 返回的函数传入的参数
- const bindArgs = Array.from(arguments)
- // 合并
- return self.apply(context, args.concat(bindArgs));
- };
- };
大家对这段代码应该都能看懂,实现原理和手写 call , apply 都很像,因为 bind 可以通过返回的函数传参,所以在 return 里面获取的 bindArgs 就是这个意思,然后最后通过 concat 把原来的参数和后来传进来的参数进行数组合并。
我们来看一下结果:
- const person = {
- name: 'zyj'
- }
-
- function man(age) {
- console.log(this.name);
- console.log(age)
- }
-
- const test = man.myBind(person)
- test(18)//zyj 18
现在重点来了,bind 区别于 call 和 apply 的地方在于它可以返回一个函数,然后把这个函数当作构造函数通过 new 操作符来创建对象。
我们来试一下:
- const person = {
- name: 'zyj'
- }
-
- function man(age) {
- console.log(this.name);
- console.log(age)
- }
-
- const test = man.myBind(person)
- const newTest = new test(18) // zyj 18
这是用的我们上面写的 myBind 函数是这个结果,那原生 bind 呢?
- const person = {
- name: 'zyj'
- }
-
- function man(age) {
- console.log(this.name);
- console.log(age)
- }
-
- const test = man.bind(person)
- const newTest = new test(18) // undefined 18
由上述代码可见,使用原生
bind生成绑定函数后,通过new操作符调用该函数时,this.name 是一个 undefined,这其实很好理解,因为我们 new 了一个新的实例,那么构造函数里的 this 肯定指向的就是实例,而我们的代码逻辑中指向的始终都是 context ,也就是传进去的参数。
所以现在我们要加个判断逻辑:
- Function.prototype.myBind = function (context) {
- // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
- const self = this;
- // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
- // 这里产生了闭包
- const args = Array.from(arguments).slice(1)
- const theBind = function () {
- const bindArgs = Array.from(arguments);
-
- // 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例
- // 当作为普通函数时,将绑定函数的 this 指向 context 即可
- // this instanceof fBound 的 this 就是绑定函数的调用者
- return self.apply(
- this instanceof theBind ? this : context,
- args.concat(bindArgs)
- );
- };
- return theBind;
- };
现在这个效果我们也实现了,那我们的 myBind 函数就和其他的原生 bind 一样了吗?来看下面的代码:
- const person = {
- name: 'zyj'
- }
- function man(age) {
- console.log(this.name);
- console.log(age)
- }
- man.prototype.sayHi = function() {
- console.log('hello')
- }
- const test = man.myBind(person)
- const newTest = new test(18) // undefined 18
- newTest.sayHi()
如果 newTest 是我们 new 出来的 man 实例,那根据原型链的知识,定义在man的原型对象上的方法肯定会被继承下来,所以我们通过 newTest.sayHi 调用能正常输出 hello 么?

该版代码的改进思路在于,将返回的绑定函数的原型对象的
__proto__属性,修改为原函数的原型对象。便可满足原有的继承关系。
- Function.prototype.myBind = function (context) {
- // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man
- const self = this;
- // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context)
- // 这里产生了闭包
- const args = Array.from(arguments).slice(1);
- const theBind = function () {
- const bindArgs = Array.from(arguments);
-
- // 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例
- // 当作为普通函数时,将绑定函数的 this 指向 context 即可
- // this instanceof fBound 的 this 就是绑定函数的调用者
- return self.apply(
- this instanceof theBind ? this : context,
- args.concat(bindArgs)
- );
- };
- theBind.prototype = Object.create(self.prototype)
- return theBind;
- };
- // 手写一个new
- function myNew(fn, ...args) {
- // 创建一个空对象
- let obj = {}
- // 使空对象的隐式原型指向原函数的显式原型
- obj.__proto__ = fn.prototype
- // this指向obj
- let result = fn.apply(obj, args)
- // 返回
- return result instanceof Object ? result : obj
- }
有很多小伙伴不明白为什么要判断 result 是不是 Object 的实例,我们首先得了解,在JavaScript中构造函数可以有返回值也可以没有。
1. 没有返回值的情况返回实例化的对象
- function Person(name, age){
- this.name = name
- this.age = age
- }
- console.log(Person()); //undefined
- console.log(new Person('zyj',20));//Person { name: 'zyj', age: 20 }
2. 如果存在返回值则检查其返回值是否为引用类型,如果为非引用类型,如(string,number,boolean,null,undefined),上述几种类型的情况与没有返回值的情况相同,实际返回实例化的对象
- function Person(name, age){
- this.name = name
- this.age = age
- return 'lalala'
- }
- console.log(Person()); //lalala
- console.log(new Person('zyj',20));//Person { name: 'zyj', age: 20 }
3. 如果存在返回值是引用类型,则实际返回该引用类型
- function Person(name, age){
- this.name = name
- this.age = age
- return {
- name: 'curry',
- ahe: 34
- }
- }
- console.log(Person()); //{ name: 'curry', ahe: 34 }
- console.log(new Person('zyj',20));//{ name: 'curry', ahe: 34 }
- const arrayLike=document.querySelectorAll('div')
-
- // 1.扩展运算符
- [...arrayLike]
- // 2.Array.from
- Array.from(arrayLike)
- // 3.Array.prototype.slice
- Array.prototype.slice.call(arrayLike)
- // 4.Array.apply
- Array.apply(null, arrayLike)
- // 5.Array.prototype.concat
- Array.prototype.concat.apply([], arrayLike)
- function father (name) {
- this.name = name
- this.age = 18
- }
-
- father.prototype.getName = function(){} // 方法定义在父类原型上(公共区域)
-
- function child () {
- // 继承父类属性,可传入参数
- father.call(this,'Tom')
- // 将会生成如下属性:
- // name:'tom'
- // age: 18
- }
- child.prototype = new father() // 重写原型对象
- child.prototype.constructor = child
这里的原型链关系应该是这样的:
该方式也叫做伪经典继承。其核心思路是:重写子类的原型对象为父类实例,并通过盗用构造函数继承父类实例的属性。
基本思路是,对传入的对象做了一次浅复制,并赋值给一个空函数
F(临时类型)的原型对象,并返回一个通过F生成的实例。这个实例的__proto__自然而然地指向了传入的对象,可以理解为一个挂钩🧷的过程。
- function object(o) {
- function F() {}
- F.prototype = o;
- return new F();
- }
-
- let father = function() {}
- father.prototype.getName = function() {
- console.log('zyj')
- }
-
- let son = object(father)
- let daughter = object(father)
- son.prototype.getName() // zyj
大概是这么个过程:

在 ECMAScript 5 中,通过增加 Object.create() 方法将原型式继承的概念规范化,即替代了上述自定义的 object() 函数。所以对于 Object.create() 的手写实现,核心思路与上述的自定义函数类似,只是添加了部分参数校验的环节。
let son = Object.create(father) // 等同于上述代码
- Object.myCreate = function(proto, propertyObject) {
- // 参数校验
- if (typeof proto !== 'object' && typeof proto !== 'function') {
- throw new TypeError('Object prototype may only be an Object or null.')
- // 不能传一个 null 值给实例作为属性
- if (propertyObject == null) {
- new TypeError('Cannot convert undefined or null to object')
- }
- // 原型式继承的思想:用一个空函数(即忽略掉原有构造函数的初始化代码)创建一个干净的实例
- function F() {}
- F.prototype = proto // 确定后续的继承关系
- const obj = new F()
-
- // 如果有传入第二个参数,将其设为 obj 的属性
- if (propertyObject != undefined) {
- Object.defineProperties(obj, propertyObject)
- }
-
- // 即 Object.create(null) 创建一个没有原型对象的对象
- if (proto === null) {
- obj.__proto__ = null
- }
- return obj
- }
ES5实现:
- function unique(arr) {
- var res = arr.filter(function(item, index, array) {
- return array.indexOf(item) === index
- })
- return res
- }
ES6实现:
var unique = arr => [...new Set(arr)]