手写 Call
call 的特点:
- 如果是null、undefined 默认指向window
- 如果是基本数据类型 字符串、数字、布尔值,会被转为包装对象
- 第一个参数为要改变的this指向
- call函数的返回值是返回被借调函数的返回值
Function.prototype.myCall = function(context, ...args) {
// 处理 null 和 undefined
if (context === null || context === undefined) {
context = window
}
// 处理基本数据类型
if (typeof context !== 'object' && typeof context !== 'function') {
context = Object(context)
}
const fnSymbol = Symbol(); // 使用Symbol以避免属性冲突
// 这样在打印的时候不会出现新增的属性,替代下面的这种写法
Object.defineProperty(context, fnSymbol, {
enumerable: false,
value: this
})
// context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
}
手写 apply
Function.prototype.myApply = function(context, ...args) {
if (context === null || context === undefined) {
context = window
}
// 处理基本数据类型
if (typeof context !== 'object' && typeof context !== 'function') {
context = Object(context)
}
const fnSymbol = Symbol(); // 使用Symbol以避免属性冲突
// 这样在打印的时候不会出现新增的属性,替代下面的这种写法
Object.defineProperty(context, fnSymbol, {
enumerable: false,
value: this
})
//context[fnSymbol] = this;
// 展开参数传递
const result = context[fnSymbol](...args[0]);
delete context[fnSymbol];
return result;
}
手写 bind
bind的特点:
- 返回一个新函数
- 并且改变新函数的 this 指向
- 返回的新函数可以作为普通函数、可以作为构造函数使用,如果是构造函数使用,则看返回的结果是否是引用数据类型,如果是,则返回该引用类型,否则忽略返回this
- 并且新函数也可以传递参数
Function.prototype.myBind = function (context, ...args1) {
var _this = this
var boundFunction = function(...args2) {
// 参数拼接
const args = args1.concat(args2)
// 判断是否是 new 调用
const isNew = this instanceof boundFunction
const _cxt = isNew ? this : context
// 原函数的返回值,就是新函数的返回值
const result = _this.apply(_cxt, args)
// 如果是构造函数调用并且返回值是引用数据类型
if (isNew && typeof result === 'object' && result !== null) {
return result
}
// 如果没有返回值但是是new调用就返回this,否则返回result
return isNew ? this : result
}
// 维护原型链,如果绑定的是构造函数,确保new操作符创建的对象实例继承自原函数的原型
if (this.prototype) {
boundFunction.prototype = Object.create(this.prototype)
}
return boundFunction
}
// 示例
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log("Hello, " + this.name);
};
// 使用原函数调用 sayHello 方法
const person1 = new Person("Alice");
person1.sayHello(); // 输出: Hello, Alice
// 使用绑定函数调用 sayHello 方法
const boundFunction = Person.myBind({ name: "Bob" });
const person2 = new boundFunction();
person2.sayHello(); // 如果不处理原型链,则会报错
- 方式2
Function.prototype.myBind = function (thisArg, ...args) {
const fn = this
return function (...args1) {
const allArgs = [...args, ...args1]
// 判断是否为new的构造函数
if (new.target) {
return new fn(...allArgs)
} else {
return fn.call(thisArg, allArgs)
}
}
}
手写 new
new 运算符做了哪些事?
- 创建一个空对象
- 设置原型链:将空对象的隐式原型指向构造函数的显示原型
- 绑定this,执行构造函数,将this指向这个新对象
- 判断返回值,如果构造函数,则返回这个对象;否则返回创建的空对象
function myNew(fn, ...args) {
// 1. 创建一个空对象
var obj = Object.create(fn.prototype)
// 2. 执行构造函数,并设置 this 指向
var result = fn.apply(obj, args)
// 3. 判断返回值类型
return result instanceof Object ? result : obj
}
手写 instanceof
用于检测构造函数的 prototype 是否出现在某个实例对象的原型链上
function myInstanceof(object, constructor) {
// 排除基本数据类型
if (typeof object !== 'object' || !object) {
return false
}
// 获取object的原型对象
let left = object.__proto__
let right = constructor.prototype
while(left) {
if (left === right) {
return true
}
left = left.__proto__
}
return false
}
手写深拷贝
function deepClone(obj) {
// 1.排除 null、undefined、函数 以及基本数据类型
if (typeof obj !== 'object' || !obj) {
return obj
}
// {}、[]、Date、Error、RegExp、Arguments...
// 2.对正则特殊处理
if (obj instanceof RegExp) {
return new RegExp(obj)
}
let result = Array.isArray(obj) ? [] : new obj.constructor()
for(const key in result) {
result[key] = deepClone(obj[key])
}
return result
}
防抖
function debounce(fn, delay=200) {
let timer = null;
return function() {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
节流
function throttle(fn, delay=200) {
var startTime = 0
var timer = null
return function() {
var endTime = Date.now()
if (endTime - startTime > delay) {
fn.apply(this, arguments)
startTime = endTime
} else { // 保证至少触发一次
clearTimeout(timer)
timer = setTimeout(() => {
fn,apply(this, arguments)
}, delay)
}
}
}
冒泡排序
function bubbleSort(arr) {
var len = arr.length
for(var i = 0; i < len - 1; i++) { // 轮数
for(var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) {
var temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
}
选择排序
function selectionSort(arr) {
const length = arr.length;
for (let i = 0; i < length - 1; i++) {
// 定义最小元素的下标
let minIndex = i;
for (let j = i + 1; j < length; j++) {
if (arr[j] < arr[minIndex]) {
// 找到小的元素就更新下标
minIndex = j;
}
}
// 交换位置
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
快速排序
实现思路:
- 随机选择一个数作为基准点
- 遍历数组,将小于基准值的数放在基准的左边,大于基准值的数放在右边
- 递归将小于基准的数组和大于基准的数组进行排序
function quickSort(arr) {
var len = arr.length
if (len <= 1) { // 小于1就不需要进行比较了
return arr
}
// 选取基准点
var pointIndex = Math.floor(len / 2)
// 截取基准点
var point = arr.splice(pointIndex, 1)
// 定义两个容器
var left = []
var right = []
for(var i = 0; i < len - 1; i++) {
if (arr[i] < point) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
// 递归+拼接
return [].concat(quickSort(left), point, quickSort(right))
}
手写 promise
思路:
- 可以改变执行器中的状态有哪些
- 调用 resolve,返回成功的promise
- 调用 reject,返回失败的promise
- 抛出错误或程序异常,返回失败的promise
- then/catch:可以链式调用,所以要返回一个promise,该promise的状态和结果需要看对应回调函数的执行结果
- 如果返回的是promise,则新返回的promise的状态跟随该promise
- 如果是错误或抛出异常,则返回失败的promise
- 其他情况,默认返回成功的promise
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function MyPromise(execute) {
// 存储promise的结果
this.result = undefined
// 存储promise的状态
this.stauts = PENDING
// 存储的回调列表
this.callbacks = []
var _this = this
// resolve 方法要做的事情:改变promise的状态变为成功,并保存结果
function resolve(res) {
_this.changeStatus(FULFILLED, res)
if (_this.callbacks.length > 0) {
_this.callbacks.forEach(function (item) {
item[FULFILLED](_this.result)
})
}
}
// reject 方法要做的事情:改变promise的状态为失败,并保存结果
function reject(res) {
_this.changeStatus(REJECTED, res)
if (_this.callbacks.length > 0) {
_this.callbacks.forEach(function (item) {
item[REJECTED](_this.result)
})
}
}
// 出现异常,改变promise状态
try {
execute(resolve, reject)
} catch (error) {
reject(error)
}
}
// 改变promise状态的函数
MyPromise.prototype.changeStatus = function (status, res) {
// 状态一旦改变了,就不能再变化了
if (this.stauts !== PENDING) return
this.result = res
this.stauts = status
}
// 封装函数,处理回调
MyPromise.prototype.handleCallback = function (cb, onFulfilledCallback, onRejectedCallback) {
try {
// 获取执行的回调函数的结果,并判断这个结果是什么类型
const result = cb(this.result)
if (result instanceof MyPromise) {
// 如果返回promise,则该promise跟随这个promise
result.then(onFulfilledCallback, onRejectedCallback)
} else {
// 否则默认都是成功的promise
onFulfilledCallback(result)
}
} catch (error) {
// 如果是异常,则返回失败的promise
onRejectedCallback(error)
}
}
// 实现 then 方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 处理值穿透的情况
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (res) { return res }
onRejected = typeof onFulfilled === 'function' ? onRejected : function (res) { return res }
// 可以链式调用,返回一个新的promise
return new MyPromise((resolve, reject) => {
if (this.stauts !== PENDING) {
this.handleCallback(this.stauts === FULFILLED ? onFulfilled : onRejected, resolve, reject)
} else {
// 先指定了回调函数(延时触发execute中的resolve/reject),需要将回调函数保存起来
this.callbacks.push({
[FULFILLED]: () => {
this.handleCallback(onFulfilled, resolve, reject)
},
[REJECTED]: () => {
this.handleCallback(onRejected, resolve, reject)
}
})
}
})
}
// 实现 catch 方法
MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
构造函数继承
function Parent(name) {
this.name = name;
}
function Child(name) {
Parent.call(this, name);
this.type = "child 的属性";
}
console.log(new Child("zs"));
- 优点:可以继承父类构造函数中的实例属性和方法
- 缺点:不能继承父类原型中的属性和方法
原型链继承
function Person() {
this.name = 'zs';
this.age = 18
}
function Student() {
this.score = 98;
}
// 方式一:这种方式会造成父类和子类的原型对象相互影响,指向的是同一个地址
Student.prototype = Person.prototype; // constructor 会指向 Object
// 方式二:这种方式会造成如果Person构造函数中返回的的并不是this,是其他的对象,那么对于Student来说则也不能访问Person原型中的属性和方法
Student.prototype = new Person(); // constructor 会指向 Person
// 方式三:推荐
Student.prototype = Object.create(Person.prototype); // constructor 会指向 Object
// 三种方式都需要设置 constructor 属性
Student.prototype.constructor = Student;
var s1 = new Student();
// 方式一的原型链为:s1.__proto__ => Student.prototype/Person.prototype => Object.prototype => null
// 方式二的原型链为: s1.__proto__ => Student.prototype/p1 => Person.prototype => Object.prototype => null
// 方式三的原型链为: s1.__proto__ => Student.prototype => Person.prototype => Object.prototype => null
- 优点:可以继承父类原型上的属性和方法
- 缺点:不能传递参数,不能使用父类构造函数中实例的属性和方法,并且每次创建的实例对象,都会创建一份新的原型数据,造成内存浪费
- 如果是通过
Student.prototype = new Person()的方式实现继承,那么会导致父类构造函数调用两次,以及父类的构造函数中的属性和方法会在子类的原型上显示,重复数据的问题
- 如果是通过
寄生组合继承
function Person(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
// 父类原型上添加的方法
Person.prototype.sayName = function () {
console.log(this.name);
};
// 子类
function Student(name, grade) {
Person.call(this, name); // 借用构造函数,实现对属性的继承
this.grade = grade;
}
// 使用寄生方式继承父类原型
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student; // 将构造函数指回子类
Student.prototype.sayGrade = function () {
console.log(this.grade);
};