二叉树层次遍历
// 二叉树层次遍历
class Node {
constructor(element, parent) {
this.parent = parent // 父节点
this.element = element // 当前存储内容
this.left = null // 左子树
this.right = null // 右子树
}
}
class BST {
constructor(compare) {
this.root = null // 树根
this.size = 0 // 树中的节点个数
this.compare = compare || this.compare
}
compare(a,b) {
return a - b
}
add(element) {
if(this.root === null) {
this.root = new Node(element, null)
this.size++
return
}
// 获取根节点 用当前添加的进行判断 放左边还是放右边
let currentNode = this.root
let compare
let parent = null
while (currentNode) {
compare = this.compare(element, currentNode.element)
parent = currentNode // 先将父亲保存起来
// currentNode要不停的变化
if(compare > 0) {
currentNode = currentNode.right
} else if(compare < 0) {
currentNode = currentNode.left
} else {
currentNode.element = element // 相等时 先覆盖后续处理
}
}
let newNode = new Node(element, parent)
if(compare > 0) {
parent.right = newNode
} else if(compare < 0) {
parent.left = newNode
}
this.size++
}
// 层次遍历 队列
levelOrderTraversal(visitor) {
if(this.root == null) {
return
}
let stack = [this.root]
let index = 0 // 指针 指向0
let currentNode
while (currentNode = stack[index++]) {
// 反转二叉树
let tmp = currentNode.left
currentNode.left = currentNode.right
currentNode.right = tmp
visitor.visit(currentNode.element)
if(currentNode.left) {
stack.push(currentNode.left)
}
if(currentNode.right) {
stack.push(currentNode.right)
}
}
}
}
// 测试
var bst = new BST((a,b)=>a.age-b.age) // 模拟sort方法
// ![](http://img-repo.poetries.top/images/20210522203619.png)
// ![](http://img-repo.poetries.top/images/20210522211809.png)
bst.add({
age: 10})
bst.add({
age: 8})
bst.add({
age:19})
bst.add({
age:6})
bst.add({
age: 15})
bst.add({
age: 22})
bst.add({
age: 20})
// 使用访问者模式
class Visitor {
constructor() {
this.visit = function (elem) {
elem.age = elem.age*2
}
}
}
// ![](http://img-repo.poetries.top/images/20210523095515.png)
console.log(bst.levelOrderTraversal(new Visitor()))
实现类的继承
类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。
function Parent(name) {
this.parent = name
}
Parent.prototype.say = function() {
console.log(`${
this.parent}: 你打篮球的样子像kunkun`)
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.child = name
}
/** 1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创建了父类原型的副本,与父类原型完全隔离*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
console.log(`${
this.parent}好,我是练习时长两年半的${
this.child}`);
}
// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk
实现apply方法
apply原理与call很相似,不多赘述
// 模拟 apply
Function.prototype.myapply = function(context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
实现双向数据绑定
let obj = {
}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
参考:前端手写面试题详细解答
实现非负大整数相加
JavaScript对数值有范围的限制,限制如下:
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER
)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER
数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) {
let res = '';
let temp = 0;
a = a.split('');
b = b.split('');
while (a.length || b.length || temp) {
temp += ~~a.pop() + ~~b.pop();
res = (temp % 10) + res;
temp = temp > 9
}
return res.replace(/^0+/, '');
}
其主要的思路如下:
- 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
- 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位
- 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加
- 重复上述操作,直至计算结束
查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是d,出现了5次
let str = "abcabcabcbbccccc";
let num = 0;
let char = '';
// 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"
// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) =>