写一个函数判断变量类型
function getType(data) {
let type = typeof data;
if (type !== "object") {
return type;
}
return Object.prototype.toString.call(data).slice(8, -1);
}
console.log(getType(1)); // number
console.log(getType(true)); // boolean
console.log(getType([1, 2, 3])); // Array
console.log(getType(/abc/)); // RegExp
console.log(getType(new Date())); // Date
console.log(getType(new Person())); // Object
function Person() {}
console.log(getType({})); // Object
如何判断数组或对象
- 通过 instanceof 进行判断
var arr = [1,2,3,1];
console.log(arr instanceof Array) // true
- 通过对象的 constructor 属性
var arr = [1,2,3,1];
console.log(arr.constructor === Array) // true
- Object.prototype.toString.call(arr)
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call([]));//[object Array]
- 可以通过 ES6 新提供的方法 Array.isArray( )
Array.isArray([]) //true
数组去重
// 数字或字符串数组去重,效率高
function unique(arr) {
var result = {}; // 利用对象属性名的唯一性来保证不重复
for (var i = 0; i < arr.length; i++) {
if (!result[arr[i]]) {
result[arr[i]] = true;
}
}
return Object.keys(result); // 获取对象所有属性名的数组
}
// 利用ES6的Set去重,适配范围广,效率一般,书写简单
function unique(arr) {
return [...new Set(arr)];
}
// 任意数组去重,适配范围广,效率低
function unique(arr) {
var result = []; // 结果数组
for (var i = 0; i < arr.length; i++) {
if (!result.includes(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
实现一个函数,对一个 url 进行请求,失败就再次请求,超过最大次数就走失败回调,任何一次成功都走成功回调
function request(url, maxCount = 5) {
return fetch(url).catch((err) => {
if (maxCount > 0) {
request(url, maxCount--);
} else {
Promise.reject(err);
}
});
}
request("www.baidu.com", 5).then((resp) => console.log(resp));
冒泡排序
function bSort(arr) {
let len = arr.length;
// 外层 for 循环控制冒泡的次数
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
// 内层 for 循环控制每一次冒泡需要比较的次数
// 因为之后每一次冒泡的两两比较次数会越来越少,所以 -i
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
//举个数组
myArr = [20, -1, 27, -7, 35];
//使用函数
console.log(bSort(myArr)); // [ -7, -1, 20, 27, 35 ]
实现一个 sleep 函数
function sleep(delay) {
var start = new Date().getTime();
while ((new Date().getTime() - start < delay) {
continue;
}
}
function test() {
console.log('111');
sleep(2000);
console.log('222');
}
test()
这种实现方式是利用一个伪死循环阻塞主线程。因为 JS 是单线程的。所以通过这种方式可以实现真正意义上的 sleep。
给定两个数组,求并集,交集,差集
const arr1 = [];
const arr2 = [];
//并集
const union = [...new Set([...arr1, ...arr2])];
//交集
const cross = [...new Set(arr.filter((it) => arr2.includes(it)))];
//差集
const diff = union.fliter((it) => !cross.includes(it));
实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。
// 完全不用 API
var getIndexOf = function (s, t) {
let n = s.length;
let m = t.length;
if (!n || !m || n < m) return -1;
for (let i = 0; i < n; i++) {
let j = 0;
let k = i;
if (s[k] === t[j]) {
k++;
j++;
while (k < n && j < m) {
if (s[k] !== t[j]) break;
else {
k++;
j++;
}
}
if (j === m) return i;
}
}
return -1;
};
// 测试
console.log(getIndexOf("Hello World", "rl"));
使用 JavaScript Proxy 实现简单的数据绑定
<body>
<input type="text" id="model">
<p id="word"></p>
</body>
<script>
const model = document.getElementById("model")
const word = document.getElementById("word")
var obj= {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log('setting',target, key, value, receiver);
if (key === "text") {
model.value = value;
word.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});
model.addEventListener("keyup",function(e){
newObj.text = e.target.value
})
</script>
打印出 1~10000 以内的对称数
function isSymmetryNum(start, end) {
for (var i = start; i < end + 1; i++) {
var iInversionNumber = +i.toString().split("").reverse().join("");
if (iInversionNumber === i && i > 10) {
console.log(i);
}
}
}
isSymmetryNum(1, 10000);
使用递归完成 1 到 100 的累加
function add(x, y) {
if (x === y) {
return x;
} else {
return y + add(x, y - 1);
}
}
console.log(add(1, 100));
产生一个不重复的随机数组
// 生成随机数
function randomNumBoth(Min, Max) {
var Range = Max - Min;
var Rand = Math.random();
var num = Min + Math.round(Rand * Range); //四舍五入
return num;
}
// 生成数组
function randomArr(len, min, max) {
if (max - min < len) {
//可生成数的范围小于数组长度
return null;
}
var hash = [];
while (hash.length < len) {
var num = randomNumBoth(min, max);
if (hash.indexOf(num) == -1) {
hash.push(num);
}
}
return hash;
}
// 测试
console.log(randomArr(10, 1, 100));
在上面的代码中,我们封装了一个 randomArr 方法来生成这个不重复的随机数组,该方法接收三个参数,len、min 和 max,分别表示数组的长度、最小值和最大值。randomNumBoth 方法用来生成随机数。
给你一个数组,计算每个数出现的次数,如果每个数组返回的数都是独一无二的就返回 true 相反则返回的 false
输入:arr = [1,2,2,1,1,3]
输出:true
解释:在该数组中,1 出现了 3 次,2 出现了 2 次,3 只出现了 1 次。没有两个数的出现次数相同。
代码示例:
function uniqueOccurrences(arr) {
let uniqueArr = [...new Set(arr)]
let countArr = []
for (let i = 0; i < uniqueArr.length; i++) {
countArr.push(arr.filter(item => item == uniqueArr[i]).length)
}
return countArr.length == new Set(countArr).size
};
// 测试
console.log(uniqueOccurrences([1, 2, 2, 1, 1, 3])); // true
console.log(uniqueOccurrences([1, 2, 2, 1, 1, 3, 2])); // false
封装一个能够统计重复的字符的函数,例如 aaabbbdddddfff 转化为 3a3b5d3f
function compression(str) {
if (str.length == 0) {
return 0;
}
var len = str.length;
var str2 = "";
var i = 0;
var num = 1;
while (i < len) {
if (str.charAt(i) == str.charAt(i + 1)) {
num++;
} else {
str2 += num;
str2 += str.charAt(i);
num = 1;
}
i++;
}
return str2;
}
// 测试:
console.log(compression('aaabbbdddddfff')); // 3a3b5d3f
实现 5.add(3).sub(2)
这里想要实现的是链式操作,那么我们可以考虑在 Number 类型的原型上添加 add 和 sub 方法,这两个方法返回新的数
Number.prototype.add = function (number) {
if (typeof number !== 'number') {
throw new Error('请输入数字~');
}
return this.valueOf() + number;
};
Number.prototype.minus = function (number) {
if (typeof number !== 'number') {
throw new Error('请输入数字~');
}
return this.valueOf() - number;
};
console.log((5).add(3).minus(2)); // 6
请实现一个模块 math,支持链式调用 math.add(2,4).minus(3).times(2);
class Math {
constructor(value) {
let hasInitValue = true;
if (value === undefined) {
value = NaN;
hasInitValue = false;
}
Object.defineProperties(this, {
value: {
enumerable: true,
value: value,
},
hasInitValue: {
enumerable: false,
value: hasInitValue,
},
});
}
add(...args) {
const init = this.hasInitValue ? this.value : args.shift();
const value = args.reduce((pv, cv) => pv + cv, init);
return new Math(value);
}
minus(...args) {
const init = this.hasInitValue ? this.value : args.shift();
const value = args.reduce((pv, cv) => pv - cv, init);
return new Math(value);
}
times(...args) {
const init = this.hasInitValue ? this.value : args.shift();
const value = args.reduce((pv, cv) => pv * cv, init);
return new Math(value);
}
divide(...args) {
const init = this.hasInitValue ? this.value : args.shift();
const value = args.reduce((pv, cv) => pv / cv, init);
return new Math(value);
}
toJSON() {
return this.valueOf();
}
toString() {
return String(this.valueOf());
}
valueOf() {
return this.value;
}
[Symbol.toPrimitive](hint) {
const value = this.value;
if (hint === 'string') {
return String(value);
} else {
return value;
}
}
}
export default new Math();
设计⼀个⽅法以判断是否回⽂(颠倒后的字符串和原来的字符串⼀样为回⽂)
function isPalindrome(str) {
if (typeof str !== 'string') {
return false
}
return str.split('').reverse().join('') === str
}
// 测试
console.log(isPalindrome('HelleH')); // true
console.log(isPalindrome('Hello')); // false
设计⼀个⽅法以统计字符串中出现最多次数的字符
function findMaxDuplicateChar(str) {
let cnt = {}; //用来记录所有的字符的出现频次
let c = ''; //用来记录最大频次的字符
for (let i = 0; i < str.length; i++) {
let ci = str[i];
if (!cnt[ci]) {
cnt[ci] = 1;
} else {
cnt[ci]++;
}
if (c == '' || cnt[ci] > cnt[c]) {
c = ci;
}
}
console.log(cnt); // { H: 1, e: 1, l: 3, o: 2, ' ': 1, W: 1, r: 1, d: 1 }
return c;
}
// 测试
console.log(findMaxDuplicateChar('Hello World')); // l
合并二维有序数组成一维有序数组,归并排序的思路
function merge(left, right) {
let result = []
while (left.length > 0 && right.length > 0) {
if (left[0] < right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
return result.concat(left).concat(right)
}
function mergeSort(arr) {
if (arr.length === 1) {
return arr
}
while (arr.lengt
h > 1) {
let arrayItem1 = arr.shift();
let arrayItem2 = arr.shift();
let mergeArr = merge(arrayItem1, arrayItem2);
arr.push(mergeArr);
}
return arr[0]
}
let arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6]];
let arr2 = [[1, 4, 6], [7, 8, 10], [2, 6, 9], [3, 7, 13], [1, 5, 12]];
console.log(mergeSort(arr1))
console.log(mergeSort(arr2))
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。(难)
首先,我们肯定需要封装一个函数,而这个函数接收一个字符串作为参数,返回不含有重复字符的子串长度。来看下面的示例:
示例 1:
输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
示例代码:
var lengthOfLongestSubstring = function (s) {
var y = [];
var temp = [];
var maxs = 0;
if (s == "") {
return 0;
}
if (s.length == 1) {
return 1;
}
for (var i = 0; i < s.length; i++) {
if (temp.includes(s[i])) {
y.push(temp.length);
temp.shift();
continue;
} else {
temp.push(s[i])
y.push(temp.length);
}
}
for (var j = 0; j < y.length; j++) {
if (maxs <= y[j]) {
maxs = y[j]
}
}
return maxs;
};
// 测试
console.log(lengthOfLongestSubstring('abcabcbb')); // 3
console.log(lengthOfLongestSubstring('bbbbb')); // 1
console.log(lengthOfLongestSubstring('pwwkew')); // 3
有一堆整数,请把他们分成三份,确保每一份和尽量相等(11,42,23,4,5,6 4 5 6 11 23 42 56 78 90)(难)
本道题目是一道考察算法的题目,主要是考察编程基本功和一定的想像力。
const testArr = [11, 42, 23, 4, 5, 6, 4, 5, 6, 11, 23, 42, 56, 78, 90];
function avarageSum(n, arr) {
// 找到平均值
const sum = arr.reduce((a, b) => a + b, 0)
const ava = Math.round(sum / n);
// 生成一个长度为n的二维数组
let target = new Array(n).fill(0).map(()=>[])
// const target = [[], [], []];
let cursor;
let max = 0;
let maxIdx = 0;
// 所有数组需要尽量加到ava
while (arr.length > 0) {
// 得到当前arr数组最大的数
// 出数器
// 出数器负责拿出当前数组中最大的那个数
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
maxIdx = i
}
}
// arr数组中删除最大数
const temparr = arr.splice(maxIdx, 1);
// 计算差距的函数 找到差距(距离平均值)最大的那一组 返回下标
cursor = getMaxDis(target, ava);
// console.log("下标", cursor)
// 加入
target[cursor].push(temparr[0]);
// 重置
max = 0;
maxIdx = 0;
}
return target.map(item=>{
// 返回累加结果
let sum = item.reduce((a,b)=>a+b,0);
item.sum = sum
return item
})
}
function getMaxDis(origin, stand) {
// 计算origin数组中和stand最大差距的那一组
const len = origin.length;
let i = 0;
let maxDis;
let maxDisIdx;
while (i < len) {
const sum = origin[i].reduce((a, b) => a + b, 0);
const dis = stand - sum;
if (i === 0) {
maxDis = dis;
maxDisIdx = 0
}
if (dis > maxDis) {
maxDis = dis;
maxDisIdx = i
}
i++;
}
return maxDisIdx
}
console.log(avarageSum(3, testArr))
/*
[
[ 90, 23, 11, 6, 5, sum: 135 ],
[ 78, 42, 11, 4, sum: 135 ],
[ 56, 42, 23, 6, 5, 4, sum: 136 ]
]
*/
手写发布订阅(难)
<body>
<div id="app">
<p>this is a test</p>
{{msg}}<input type="text" v-model="msg" />{{msg}}
</div>
<script src="./index.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
msg: "",
},
});
</script>
</body>
/*
1. 创建 Vue 构造函数
在 Vue 构造函数中,调用了 observer 函数,该函数的作用就是对数据进行劫持
劫持具体要做的事儿:复制一份数据,但是不是单纯的复制,而是增加了 getter、setter
2. 书写 compile 函数。该函数主要作用于模板,从模板里面要提取信息
提取的东西主要有两个:{{}} 和 v-model
3. 创建发布者 Dep 的构造函数,如果数据发生变化,发布者就会遍历内部的数组(花名册),通知订阅者修改数据
4. 创建订阅者 Watcher 的构造函数,如果有数据的变化,发布者就会通知订阅者,订阅者上面存在 update 方法,会进行修改
*/
function Vue(options) {
// this 代表 Vue 的实例对象,本例中就是 vm
// options.data 这就是实际的数据 {msg : 'xiejie'}
observer(this, options.data);
this.$el = options.el;
compile(this);
}
// 用于对模板进行信息提取,主要提取 {{}} 和 v-model,然后进行一些操作
// {{ }} 会成为观察者,v-model 所对应的控件来绑定事件
function compile(vm) {
var el = document.querySelector(vm.$el); // el 所对应的值为 <div id="app">...</div>
var documentFragment = document.createDocumentFragment(); // 创建了一个空的文档碎片
var reg = /\{\{(.*)\}\}/; // 创建正则表达式 匹配 {{ }}
while (el.childNodes[0]) {
var child = el.childNodes[0]; // 将第一个子节点存储到 child
if (child.nodeType == 1) {
// 如果能够进入此 if,说明该节点是一个元素节点
for (var key in child.attributes) {
// 遍历该元素节点的每一个属性,拿到的就是 type="text" v-model="msg"
var attrName = child.attributes[key].nodeName; // 获取属性名 type、v-model
if (attrName === "v-model") {
var vmKey = child.attributes[key].nodeValue; // 先获取属性值,也就是 msg
// 为该节点,也就是 <input type="text" v-model="msg"> 绑定一个 input 事件
child.addEventListener("input", function (event) {
vm[vmKey] = event.target.value; // 获取用户输入的值,然后改变 vm 里面的 msg 属性对应的值,注意这里会触发 setter
});
}
}
}
if (child.nodeType == 3) {
// 如果能进入此 if,说明该节点是一个文本节点
if (reg.test(child.nodeValue)) {
// 如果能够进入到此 if,说明是 {{ }},然后我们要让其成为订阅者
var vmKey = RegExp.$1; // 获取正则里面的捕获值,也就是 msg
// 实例化一个 Watcher(订阅者),接收 3 个参数:Vue 实例,该文本节点,捕获值 msg
new Watcher(vm, child, vmKey);
}
}
documentFragment.appendChild(el.childNodes[0]); // 将第一个子节点添加到文档碎片里面
}
// 将文档碎片中节点重新添加到 el,也就是 <div id="app"></div> 下面
el.appendChild(documentFragment);
}
// 新建发布者构造函数
function Dep() {
// 将观察者添加到发布者内部的数组里面
// 这样以便于通知所有的观察者去更新数据
this.subs = [];
}
Dep.prototype = {
// 将 watcher 添加到发布者内置的数组里面
addSub: function (sub) {
this.subs.push(sub);
},
// 遍历数组里面所有的 watcher,通知它们去更新数据
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
});
},
};
// 新建观察者 Watcher 构造函数
// 接收 3 个参数:Vue 实例,文本节点 {{ msg }} 以及捕获内容 msg
function Watcher(vm, child, vmKey) {
this.vm = vm; // vm
this.child = child; // {{ msg }}
this.vmKey = vmKey; // msg
Dep.target = this; // 将该观察者实例对象添加给 Dep.target
this.update(); // 执行节点更新方法
Dep.target = null; // 最后清空 Dep.target
}
Watcher.prototype = {
// 节点更新方法
update: function () {
// 相当于:{{ msg }}.nodeValue = this.vm['msg']
// 这样就更新了文本节点的值,由于这里在获取 vm.msg,所以会触发 getter
this.child.nodeValue = this.vm[this.vmKey];
},
};
// 该函数的作用是用于数据侦听
function observer(vm, obj) {
var dep = new Dep(); // 新增一个发布者:发布者的作用是告诉订阅者数据已经更改
// 遍历数据
for (var key in obj) {
// 将数据的每一项添加到 vm 里面,至此,vm 也有了每一项数据
// 但是不是单纯的添加,而是设置了 getter 和 setter
// 在获取数据时触发 getter,在设置数据时触发 setter
Object.defineProperty(vm, key, {
get() {
console.log("触发get了");
// 触发 getter 时,将该 watcher 添加到发布者维护的数组里面
if (Dep.target) {
dep.addSub(Dep.target); // 往发布者的数组里面添加订阅者
}
console.log(dep.subs);
return obj[key];
},
set(newVal) {
console.log("触发set了");
obj[key] = newVal;
dep.notify(); // 发布者发出消息,通知订阅者修改数据
},
});
}
}
手写用 ES6proxy 如何实现 arr[-1] 的访问
const proxyArray = (arr) => {
const length = arr.length;
return new Proxy(arr, {
get(target, key) {
key = +key;
while (key < 0) {
key += length;
}
return target[key];
},
});
};
var a = proxyArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log(a[1]); // 2
console.log(a[-10]); // 9
console.log(a[-20]); // 8
提取高度嵌套的对象里的指定属性?
一般会使用递归的方式来进行查找。下面是一段示例代码:
function findKey(data, field) {
let finding = '';
for (const key in data) {
if (key === field) {
finding = data[key];
}
if (typeof (data[key]) === 'object') {
finding = findKey(data[key], field);
}
if (finding) {
return finding;
}
}
return null;
}
// 测试
console.log(findKey({
name: 'zhangsan',
age: 18,
stuInfo: {
stuNo: 1,
classNo: 2,
score: {
htmlScore: 100,
cssScore: 90,
jsScore: 95
}
}
}, 'cssScore')); // 90