常见JS代码实现
- 用js打印一个乘法表
- 斐波那契数列函数(30以内)
- 手写Promise、手写防抖节流、手写vue响应式、手写算法
- 不用sort实现排序比如输入[3,2,6,9,1,4,8] 返回排序后的数组 - 冒泡,快排(空间时间复杂度)
- 用js实现一个permute函数,输入数字123, 打印出这三个数字的全排列(递归和回溯)
- 代码笔试题
手写对象继承,闭包,原型链,作用域,this指向,对象继承,深浅拷贝,进程线程,同步异步,防抖节流
单行写一个评级组件:"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);
实现防抖和节流
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function() {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments)
}, 500)
}
}
function sayHi() {
console.log('防抖成功');
}
document.getElementById('inp').addEventListener('input', debounce(sayHi)) // 防抖
function throttle(fn) {
let canRun = true // 通过闭包保存一个标记
return function() {
if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
canRun = false // 立即设置为 false
setTimeout(() => {
fn.apply(this, arguments); // 将外部传入的函数的执行放在 setTimeout 中
canRun = true; // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被return 掉
}, 500)
}
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight)
}
window.addEventListener('resize', throttle(sayHi))
堆栈内存 + 闭包作用域
// example 1
var a = 0,
b = 0;
function A(a){
A = function(b) {
alert(a + b++);
}
alert(a++);
}
A(1); // '1'
A(2); // '4'
console.log(a); // 0
console.log(b); // 0
// example 2
let a = {},
b = {n: 1},
c = {m: 2};
a[b] = 'Hello';
a[c] = 'World';
console.log(a[b]); // World,key会转化成字符串[Obejct object]
// example 3
let test = (function(i) {
return function(){ alert(i *= 2); }
})(2);
test(5); // '4',alert弹出的会转化成字符串
// example 3
var a =0, b = 0;
function A(a) {
A = function (b) {
alert(a + b++);
};
alert(a++);
}
A(1); // '1',执行完 `stack` 不销毁,a = 2
A(2); // '4'
深浅拷贝
function deepClone(obj){
// 过滤特殊情况
if (obj == null) return null;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
if (typeof obj !== "object") return obj;
if (typeof obj === "function") return new obj.construtor; // Funciton 克隆,待补充。。。。。。
// 不直接粗昂见空对象目的:克隆的结果和之前保持相同的所属类
let newObj = new obj.construtor;
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
}
面向对象(阿里)
function Foo() {
getName = function (){
console.log(1);
}
return this;
}
Foo.getName = function () {
console.log(2);
}
Foo.prototype.getName = function (){
console.log(3);
}
var getName = function () {
console.log(4);
}
function getName() {
console.log(5);
}
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1,window.getName()
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
EventLoop(头条)
事件循环:同步、异步、宏任务、微任务
宏任务:定时器、事件绑定
微任务:promise、async、await、ajax
- 浏览器是多线程的
JavaScript
是单线程 => 浏览器只给了其一个线程来渲染
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/*
'script start'
'async1 start'
'async2'
'promise1'
'script end'
'async1 end'
'promise2'
// 等待浏览器反应时间后
'setTimeout'
*/
使下面输出1
var a = ?;
if(a == 1 && a == 2 && a == 3){
console.log(1);
}
var a = {
toString = function*(){
yield 1;
yield 2;
yield 3;
}
}
JS模拟实现双向数据绑定
<div>
姓名:<span id="spanName"></span> <br />
<input type="text" id="inputName" />
</div>
let obj = {name: ''};
// definProperty
let newObj = JSON.parse(JSON.stringify(obj));
Object.defineProperty(obj, 'name', {
get() {
return newObj.name;
},
set(val) {
if(val === newObj.name) return;
newObj.name = val;
observer();
}
})
// proxy
obj = new Proxy(obj, {
get(target, prop) {
return target[prop];
},
set(target, prop, value) {
target[prop] = value;
}
})
// 共用
function observer() {
spanName.innerHTML = obj.name;
inputName.value = obj.name
}
observer();
// 当输入时,改变展示文本
inputName.oninput = function() {
obj.name = this.value;
}
输入m.n参数,获取一个m长度的都是n的数组(不能用循环)
// 递归实现
function getArr(m, n){
var arr = [];
function addArr(){
if (arr.length == m) return arr; // 递归结束条件
arr.push(n);
return addArr(); // 这里必须要写return,否则输出为undefined
}
return addArr();
}
console.log(getArr(3, 0));
随机生成长度为n,且值在[min-max]范围内
function generateRandomArr(n, min, max) {
var arr = [];
for (var i = 0; i < n; i++) {
var random = Math.floor(Math.random() * (max - min + 1) + min);
arr.push(random);
}
return arr;
}
实现MVVM
必要函数:
- 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
- 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 4、mvvm入口函数,整合以上三者
// 创建一个Mvvm构造函数
// 这里用es6方法将options赋一个初始值,防止没传,等同于options || {}
function Mvvm(options = {}) {
// vm.$options Vue上是将所有属性挂载到上面
// 所以我们也同样实现,将所有属性挂载到了$options
this.$options = options;
// this._data 这里也和Vue一样
let data = this._data = this.$options.data;
// 数据劫持
observe(data);
}
输入m.n参数,获取一个m长度的都是n的数组,不能用循环
递归实现深拷贝
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
js中删除两个数组中id相同的对象
const arr1 = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];
const arr2 = [{ id: 1 }, { id: 2 }, { id: 3 }];
const idList = arr2.map(v => v.id)
const newList = arr1.filter(item => {
return !idList.includes(item.id)
})
console.log(newList)
js 数组对象的某个属性排序
list.sort((a, b) => { return a.position - b.position
模拟实现JS的call和apply方法
// 浏览器环境 非严格模式
function getGlobalObject(){
return this;
}
function generateFunctionCode(argsArrayLength){
var code = 'return arguments[0][arguments[1]](';
for(var i = 0; i < argsArrayLength; i++){
if(i > 0){
code += ',';
}
code += 'arguments[2][' + i + ']';
}
code += ')';
return code;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){
if(typeof this !== 'function'){
throw new TypeError(this + ' is not a function');
}
if(typeof argsArray === 'undefined' || argsArray === null){
argsArray = [];
}
if(argsArray !== new Object(argsArray)){
throw new TypeError('CreateListFromArrayLike called on non-object');
}
if(typeof thisArg === 'undefined' || thisArg === null){
thisArg = getGlobalObject();
}
thisArg = new Object(thisArg);
var __fn = '__' + new Date().getTime();
var originalVal = thisArg[__fn];
var hasOriginalVal = thisArg.hasOwnProperty(__fn);
thisArg[__fn] = this;
var code = generateFunctionCode(argsArray.length);
var result = (new Function(code))(thisArg, __fn, argsArray);
delete thisArg[__fn];
if(hasOriginalVal){
thisArg[__fn] = originalVal;
}
return result;
};
Function.prototype.callFn = function call(thisArg){
var argsArray = [];
var argumentsLength = arguments.length;
for(var i = 0; i < argumentsLength - 1; i++){
argsArray[i] = arguments[i + 1];
}
return this.applyFn(thisArg, argsArray);
}
模拟实现ES5的apply、call、bind方法
//简单模拟Symbol属性
function jawilSymbol(obj) {
var unique_proper = "00" + Math.random();
if (obj.hasOwnProperty(unique_proper)) {
arguments.callee(obj)//如果obj已经有了这个属性,递归调用,直到没有这个属性
} else {
return unique_proper;
}
}
//原生JavaScript封装apply方法,第五版
Function.prototype.applyFive = function(context) {
var context = context || window
var args = arguments[1] //获取传入的数组参数
var fn = jawilSymbol(context);
context[fn] = this //假想context对象预先不存在名为fn的属性
if (args == void 0) { //没有传入参数直接执行
return context[fn]()
}
var fnStr = 'context[fn]('
for (var i = 0; i < args.length; i++) {
//得到"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行
fnStr += i == args.length - 1 ? args[i] : args[i] + ','
}
fnStr += ')'
var returnValue = eval(fnStr) //还是eval强大
delete context[fn] //执行完毕之后删除这个属性
return returnValue
}
//简单模拟call函数
Function.prototype.callOne = function(context) {
return this.applyFive(([].shift.applyFive(arguments)), arguments)
//巧妙地运用上面已经实现的applyFive函数
}
//简单模拟bind函数
Function.prototype.bind = Function.prototype.bind || function (context) {
var me = this;
var args = Array.prototype.slice.callOne(arguments, 1);
var F = function () {};
F.prototype = this.prototype;
var bound = function () {
var innerArgs = Array.prototype.slice.callOne(arguments);
var finalArgs = args.concat(innerArgs);
return me.applyFive(this instanceof F ? this : context || this, finalArgs);
}
bound.prototype = new F();
return bound;
}
根据promiseA+实现一个自己的promise
function resolvePromise(promise2,x,resolve,reject){
//判断x是不是promise
//规范中规定:我们允许别人乱写,这个代码可以实现我们的promise和别人的promise 进行交互
if(promise2 === x){//不能自己等待自己完成
return reject(new TypeError('循环引用'));
};
// x是除了null以外的对象或者函数
if(x !=null && (typeof x === 'object' || typeof x === 'function')){
let called;//防止成功后调用失败
try{//防止取then是出现异常 object.defineProperty
let then = x.then;//取x的then方法 {then:{}}
if(typeof then === 'function'){//如果then是函数就认为他是promise
//call第一个参数是this,后面的是成功的回调和失败的回调
then.call(x,y => {//如果Y是promise就继续递归promise
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},r => { //只要失败了就失败了
if(called) return;
called = true;
reject(r);
});
}else{//then是一个普通对象,就直接成功即可
resolve(x);
}
}catch (e){
if(called) return;
called = true;
reject(e)
}
}else{//x = 123 x就是一个普通值 作为下个then成功的参数
resolve(x)
}
}
class Promise {
constructor (executor){
//默认状态是等待状态
this.status = 'panding';
this.value = undefined;
this.reason = undefined;
//存放成功的回调
this.onResolvedCallbacks = [];
//存放失败的回调
this.onRejectedCallbacks = [];
let resolve = (data) => {//this指的是实例
if(this.status === 'pending'){
this.value = data;
this.status = "resolved";
this.onResolvedCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if(this.status === 'pending'){
this.reason = reason;
this.status = 'rejected';
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try{//执行时可能会发生异常
executor(resolve,reject);
}catch (e){
reject(e);//promise失败了
}
}
then(onFuiFilled,onRejected){
//防止值得穿透
onFuiFilled = typeof onFuiFilled === 'function' ? onFuiFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected :err => {throw err;}
let promise2;//作为下一次then方法的promise
if(this.status === 'resolved'){
promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
try{
//成功的逻辑 失败的逻辑
let x = onFuiFilled(this.value);
//看x是不是promise 如果是promise取他的结果 作为promise2成功的的结果
//如果返回一个普通值,作为promise2成功的结果
//resolvePromise可以解析x和promise2之间的关系
//在resolvePromise中传入四个参数,第一个是返回的promise,第二个是返回的结果,第三个和第四个分别是resolve()和reject()的方法。
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
},0)
});
}
if(this.status === 'rejected'){
promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
try{
let x = onRejected(this.reason);
//在resolvePromise中传入四个参数,第一个是返回的promise,第二个是返回的结果,第三个和第四个分别是resolve()和reject()的方法。
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
},0)
});
}
//当前既没有完成也没有失败
if(this.status === 'pending'){
promise2 = new Promise((resolve,reject) => {
//把成功的函数一个个存放到成功回调函数数组中
this.onResolvedCallbacks.push( () =>{
setTimeout(() => {
try{
let x = onFuiFilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
},0)
});
//把失败的函数一个个存放到失败回调函数数组中
this.onRejectedCallbacks.push( ()=>{
setTimeout(() => {
try{
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
})
}
return promise2;//调用then后返回一个新的promise
}
catch (onRejected) {
// catch 方法就是then方法没有成功的简写
return this.then(null, onRejected);
}
}
Promise.all = function (promises) {
//promises是一个promise的数组
return new Promise(function (resolve, reject) {
let arr = []; //arr是最终返回值的结果
let i = 0; // 表示成功了多少次
function processData(index, data) {
arr[index] = data;
if (++i === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
processData(i, data)
}, reject)
}
})
}
// 只要有一个promise成功了 就算成功。如果第一个失败了就失败了
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
// 生成一个成功的promise
Promise.resolve = function(value){
return new Promise((resolve,reject) => resolve(value);
}
// 生成一个失败的promise
Promise.reject = function(reason){
return new Promise((resolve,reject) => reject(reason));
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise( (resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
module.exports = Promise;