实现简易版方法
call
- 参数: 一个或多个参数;第一个为对象,后面若干为数据。
- 执行方式: 立刻执行。
- 实现原理: 在对象上添加一个属性,属性的值为当前对象,这样可以改变当前对象的指向了。
举例子
function showName() {
console.log(name);
}
let person = {
name: 'mochixuan',
age: 20
}
person.temp = showName;
person.temp(); //调用
实现
Function.prototype.call1 = function () {
// 数组解构,获取参数
let [thisArgs,...args] = [...arguments];
thisArgs = thisArgs || window;
// 使用Symbol可以防止原有属性被替换。
const tempArg = Symbol('call1');
thisArgs[tempArg] = this;
let result;
if (args == undefined) {
result = thisArgs[tempArg]()
} else {
result = thisArgs[tempArg](...args)
}
delete thisArgs[tempArg]
return result;
}
apply
- 参数: 参数一个或多个参数,第一个为对象,后面为数组的数据。
- 执行方式: 立刻执行。
- 实现原理: 在对象上添加一个属性,属性的值为当前对象,这样可以改变当前对象的指向了。
Function.prototype.apply1 = function (thisArgs,args) {
thisArgs = thisArgs || window;
const tempArg = Symbol('apply1');
thisArgs[tempArg] = this;
let result;
if (args == undefined) {
result = thisArgs[tempArg]()
} else {
result = thisArgs[tempArg](...args)
}
delete thisArgs[tempArg]
return result;
}
bind
- 参数: 参数一个或多个参数,第一个为对象,后面为的数据。
- 执行方式: 返回函数,函数被调用后执行。
- 实现原理: 生成一个函数,函数内部使用call改变this指向。
- 注意事项: 生成的bind函数,如果使用new进行实例化,this还是指向原来的对象。
Function.prototype.bind1 = function (thisArg) {
if (typeof this !== 'function') return new TypeError(this+'must be function')
const arg1 = Array.prototype.slice.call(arguments,1);
const self = this;
const bound = function (args) {
const arg2 = Array.prototype.slice.call(arguments);
const allArgs = arg1.concat(arg2);
if (this instanceof bound) {
if (self.prototype) {
function TempFC() {}
TempFC.prototype = self.prototype;
bound.prototype = new TempFC();
}
return self.apply(this,allArgs);
} else {
return self.apply(thisArg,allArgs);
}
}
return bound;
}
深拷贝
- 实现原理: 递归。
- 注意事项: 1. 反正循环引用造成死循环。2. Date、正则表达式、函数要进行特殊处理这里就不考虑了。
- 建议项目开发使用:lodash里的方法。
死循环例子
let a = {x: 1,y: 2};
a.z = a;
// Map: Map对象保存键值对,类似于数据结构字典;与传统上的对象只能用字符串当键不同,Map对象可以使用任意值当键
// WeakMap: 对象保存键值对,与Map不同的是其键必须是对象,因为键是弱引用,在键对象消失后自动释放内存.
function deepCopy(data,map = new WeakMap()) {
if (!data || typeof data !== 'object') return data; // null也是object
const result = Array.isArray(data) ? [] : {};
// 解决死循环问题
if (map.has(data)) {
return map.get(data);
} else {
map.set(data,result);
}
for (let key in data) {
if (key != null && data.hasOwnProperty(key)) { //原型链上的可枚举属性,去除原型链上的数据
if (typeof data === 'object') {
result[key] = deepCopy(data[key],map);
} else {
result[key] = data[key];
}
}
}
return result;
}
浅比较: PureComponent里用到了
- 实现原理: 对象则比较第一层数据,基本数据类型直接比较,对象比较引用。
// 该方法会忽略掉那些从原型链上继承到的属性
const hasOwnProperty = Object.prototype.hasOwnProperty
function is(x, y) {
// === 严格判断适用于对象和原始类型。但是有个例外,就是NaN和正负0。
if (x === y) {
//这个是个例外,为了针对0的不同,譬如 -0 === 0 : true
// (1 / x) === (1 / y)这个就比较有意思,可以区分正负0, 1 / 0 : Infinity, 1 / -0 : -Infinity
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
// 这个就是针对上面的NaN的情况: parseInt('abc') // NaN
return x !== x && y !== y
}
}
function shallowEqual(objA, objB) {
// Object.is()
if (is(objA, objB)) {
return true;
}
// null 也是对象
//下面这个就是,如果objA和objB其中有个不是对象或者有一个是null, 那就认为不相等。
//不是对象,或者是null.我们可以根据上面的排除来猜想是哪些情况:
//有个不是对象类型或者有个是null,那么我们就直接返回,认为他不同。其主要目的是为了确保两个都是对象,并且不是null。
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
return false
}
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
//这里只比较了对象A和B第一层是否相等
for (var i = 0; i < keysA.length; i++) {
if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
return false;
}
}
return true;
}
常见算法
冒泡排序
- 原理: 比较相邻的元素。如果第一个比第二个大,就交换他们两个.
- 时间复杂度: O(n^2)
function bubbleSort(items) {
for (let i = 0 ; i < items.length - 1; i++) {
for (let j = 0 ; j < items.length - 1 - i ; j++) {
if (items[j] > items[j+1]) {
let temp = items[j];
items[j] = items[j+1];
items[j+1] = temp;
}
}
}
return items;
}
选择排序
- 规则: 在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止
- 时间复杂度O(n^2)
- 交换次数比冒泡排序少,性能上要好于冒泡排序
function selectSort(items) {
for (let i = 0 ; i < items.length ; i++) {
let tempIndex = i;
for (let j = i+1 ; j < items.length ; j++) {
if (items[j] < items[tempIndex]) {
tempIndex = j;
}
}
if (i != tempIndex) {
let temp = items[i];
items[i] = items[tempIndex];
items[tempIndex] = temp;
}
}
return items;
}
快速排序
- 原理: 二分法,递归实现,每次得到一个正确的位置。
- 时间复杂度: O(nlogn).
function quick(items,low,high) {
if (low == null && high == null) {
low = 0;
high = items.length - 1;
}
if (low >= high) return items;
let middle = getMiddle(items,low,high);
if (middle > low) quick(items,low,middle-1);
if (middle < high) quick(items,middle+1,high)
return items;
}
function getMiddle(items,start,end) {
let temp = items[start];
while (start < end) {
while (start < end && items[end] >= temp) --end;
items[start] = items[end];
while (start < end && items[start] <= temp) ++start;
items[end] = items[start];
}
items[start] = temp;
return start;
}
二叉树查找: 最大值、最小值、固定值
- 原理: 遍历
- 时间复杂度
function queryMinMaxFix(node,fix,result) {
if (result == null) result = {};
if (node != null) {
if (result.min == null) result.min = node.value;
if (result.max == null) result.max = node.value;
if (result.fix == null) result.fix = [];
if (node.value < result.min) result.min = node.value;
if (node.value > result.max) result.max = node.value;
if (node.value == fix) result.fix.push(node);
if (node.left != null) queryMinMaxFix(node.left, fix, result);
if (node.right != null) queryMinMaxFix(node.right, fix, result);
}
return result;
}
二叉树遍历
- 原理: 递归
function traversal(node,tempOrderTraversal) {
if (node != null) {
// tempOrderTraversal.push(node.value) 前序遍历
if (node.left != null) {
preOrderTraversal(node.left,tempOrderTraversal)
}
// tempOrderTraversal.push(node.value) 中序遍历
if (node.right != null) {
preOrderTraversal(node.right,tempOrderTraversal)
}
// tempOrderTraversal.push(node.value) 后序遍历
}
}
不能使用递归时,则使用栈就是JS的数组push、pop
// 非递归遍历
var kthSmallest = function(root, k) {
const tempArr = [];
let result;
tempArr.push(root);
while (tempArr.length > 0) {
result = tempArr.pop();
if (result.value == k) break;
if (result.left != null) tempArr.push(result.left);
if (result.right != null) tempArr.push(result.right);
}
return result;
};
二叉树的最大深度
- 最大深度是从根节点到最远叶节点的最长路径上的节点数
- 原理: 遍历
var maxDepth = function(root) {
let nodeArrs = [];
if (root != null) nodeArrs.push(root);
let result = 0;
while(nodeArrs.length > 0) {
result++;
let temp = [];
for(let i = 0 ; i < nodeArrs.length ; i++) {
if (nodeArrs[i].left != null) temp.push(nodeArrs[i].left);
if (nodeArrs[i].right != null) temp.push(nodeArrs[i].right);
}
nodeArrs = temp;
}
return result;
};
给予链表中的任一节点,把它删除掉
- 编写一个函数来删除单链表中的节点(尾部除外),只允许访问该节点
var deleteNode = function(node) {
if (node.next != null) {
node.val = node.next.val;
node.next = node.next.next
}
};
链表倒叙
- 原理: 借用临时数据。
- 时间复杂度: n
function reserveLink(head) {
let result = null;
let temp = null;
while (head) {
temp = head.next;
head.next = result;
result = head;
head = temp;
}
return result;
}
如何判断一个单链表有环
- 原理:使用两个临时指针,一个指针每次加一.next,一个每次加二.next.next,当它们相等时就是有环,一般会在一次循环后相遇如果有环的化。
- 时间复杂度: n
- 常见解法: Set存储每个Node节点,has存在时则闭环,不存在则不必环
function hasRing(root) {
if(root == null) return false;
let oneNode = root;
let twoNode = root;
while(twoNode) {
if (twoNode.value === oneNode.value) {
return true;
}
oneNode = oneNode.next;
if (twoNode.next != null) {
twoNode = twoNode.next.next;
}
}
return false;
}
给定一个有序数组,找出两个数相加为一个目标数
- 原理: 用两个指针。一般手写出现比较多的是三个数相加等于一个数
- 时间复杂度: n
let data = [1,3,5,9,11];
let target = 8;
output: 3,5 或者是下标
function(data,target) {
let i = 0;
let j = data.length - 1;
for( ; i < j ;) {
if (data[i] + data[j] === target) {
break;
} else if (data[i] + data[j] > target) {
j--;
} else {
j++;
}
}
return [i,j];
}
找出一个无序数组中出现超过一半次数的数字
- 原理: 超过一半有个特点,假设超过一般的那个数为X,则X>n/2,可以抵消法,同性相加,异性相抵。
function (data) {
let x = data[0];
let total = 0;
for(let i = 0 ; i < data.length ; i++) {
if (x === data[i]) {
total = total + 1;
} else {
total = total - 1;
}
if (total === 0 && i < data.length - 1) {
x = data[i+1];
}
}
return x;
}