分享一篇自己收集的比较好的前端算法题

闭包

function fun(n,o){
    console.log(o);
    return {
        fun:function(m){
            return fun(m,n);
        }
    }
}

var a=fun(0);  // undefined
a.fun(1);  // 0
a.fun(2);  // 0
a.fun(3);  // 0

var b=fun(0).fun(1).fun(2).fun(3);  
// undefined, 0, 1, 2

var c=fun(0).fun(1);  // undefined, 0
c.fun(2);  // 1
c.fun(3);  // 1

查询字符串中出现次数最多的字符

var str = "zhaochucichuzuiduodezifu";
var o = {};
for (var i = 0, length = str.length; i < length; i++) {
// var char = str[i];
var char = str.charAt(i);
if (o[char]) { //char就是对象o的一个属性,o[char]是属性值,o[char]控制出现的次数
o[char]++; //次数加1
} else {
o[char] = 1; //若第一次出现,次数记为1
}
}
console.log(o); //输出的是完整的对象,记录着每一个字符及其出现的次数
//遍历对象,找到出现次数最多的字符和次数
var max = 0;
var maxChar = null;
for (var key in o) {
if (max < o[key]) {
max = o[key]; //max始终储存次数最大的那个
maxChar = key; //那么对应的字符就是当前的key
}
}
console.log("最多的字符是" + maxChar);
console.log("出现的次数是" + max);

reduce的用法
1、数组求和

var numbers = [1, 2, 3, 4];
function getSum(total, num) {
    return total + num;
}
console.log(numbers.reduce(getSum)) //10

2、数组扁平化

var rel=[[0, 1], [2, 3], [4, 5]].reduce(function(a, b) {
    return a.concat(b);
});
console.log(rel) //[0, 1, 2, 3, 4, 5]

3、数组最大值

var numbers = [1,10,100,0];
var max = numbers.reduce(function(pre,cur,inde,arr){return pre>cur?pre:cur;}); 
console.log(max)  //100

4、数组转对象

var arr = [{name: 'tony', age: 18}, {name: 'fly', age: 20}];
var obj = arr.reduce((prev, cur) => {prev[cur.age] = cur; return prev;}, {});
console.log(obj) 
//18:{name: "tony", age: 18}
//20:{name: "fly", age: 20}

5、统计字符串中每个字符出现的次数

var arr = 'abcdaabc';
var info = arr.split('').reduce((p, k) => (p[k]++ || (p[k] = 1), p), {});
console.log(info); //{ a: 3, b: 2, c: 2, d: 1 }

6、数组去重

Array.prototype.unique = function() {
  var sortArr = this.sort(), result = [];
  sortArr.reduce((v1,v2) => {
    if(v1 !== v2){
      result.push(v1);
    }
    return v2;
  })
  result.push(sortArr[sortArr.length - 1]);
  return result;
}
//第二种 new Set()
function distinct(a, b) {
    return Array.from(new Set([...a, ...b]))
}

// 第三种 for…of + Object  耗时最少
function distinct(a, b) {
    let arr = a.concat(b)
    let result = []
    let obj = {}

    for (let i of arr) {
        if (!obj[i]) {
            result.push(i)
            obj[i] = 1
        }
    }

    return result
}

求下面代码输出

var y = 1;
if (function f(){}) {
    y += typeof f;
}
console.log(y);  // 1undefined

等价于->

var k = 1;
if (1) {
    eval(function foo(){});
    k += typeof foo;
}
console.log(k);  // 1undefined

不同于->

var k = 1;
if (1) {
    function foo(){};
    k += typeof foo;
}
console.log(k); // output 1function

写一个mul函数

function mul (x) {
    return function (y) { // anonymous function 
        return function (z) { // anonymous function 
            return x * y * z; 
        };
    };
}

清空数组

var arrayList =  ['a','b','c','d','e','f'];
// 方法1
arrayList = [];
// 方法2
arrayList.length = 0;
// 方法3
arrayList.splice(0, arrayList.length);

判断一个object是否是数组
方法1:使用 Object.prototype.toString 来判断是否是数组

function isArray(obj){
    return Object.prototype.toString.call( obj ) === '[object Array]';
}

这里使用call来使 toString 中 this 指向 obj。进而完成判断

方法二:使用 原型链 来完成判断

function isArray(obj){
    return obj.__proto__ === Array.prototype;
}

基本思想是利用 实例如果是某个构造函数构造出来的那么 它的 __proto__是指向构造函数的 prototype属性。

下面代码输出什么

var output = (function(x){
    delete x;
    return x;
})(0);
  
console.log(output);

输出是 0。 delete 操作符是将object的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。

var x = 1;
var output = (function(){
    delete x;
    return x;
})();

console.log(output);

输出是 1。delete 操作符是将object的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。

var x = { foo : 1};
var output = (function(){
    delete x.foo;
    return x.foo;
})();

console.log(output);

输出是 undefined。x虽然是全局变量,但是它是一个object。delete作用在x.foo上,成功的将x.foo删去。所以返回undefined

var Employee = {
    company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);

输出是 xyz,这里的 emp1 通过 prototype 继承了 Employee的 company。emp1自己并没有company属性。所以delete操作符的作用是无效的。

var trees = ["redwood","bay","cedar","oak","maple"];
delete trees[3];
console.log(trees);

当我们使用 delete 操作符删除一个数组中的元素,这个元素的位置就会变成一个占位符。打印出来就是undefined x 1。 注意如果我们使用trees[3] === 'undefined × 1’返回的是 false。因为它仅仅是一种打印表示,并不是值变为undefined x 1。

var trees = ["xyz","xxxx","test","ryan","apple"];
delete trees[3];

console.log(trees.length);

输出是5。因为delete操作符并不是影响数组的长度。

var bar = true;
console.log(bar + 0);   
console.log(bar + "xyz");  
console.log(bar + true);  
console.log(bar + false);   

输出是
1
truexyz
2
1

下面给出一个加法操作表

Number + Number -> 加法
Boolean + Number -> 加法
Boolean + Boolean -> 加法
Number + String -> 连接
String + Boolean -> 连接
String + String -> 连接

(function()  {    
    return typeof arguments;
        }
        )
();

答案:“object”
arguments是对象,伪数组有两件事要注意这里:
参数不是数组,它是一个数组一样的物体,你可以使用方括号和整数索引的元素,但方法通常可在一个如推上不存在参数数组
Array.prototype.slice.call(arguments); 转成数组,当然arguments即使是数组,返回的依然是”object”,因为数组也是对象。

var f = function g(){ return 23; };    
typeof g();

答案:会发生错误
因为function g(){ return 23; }是函数表达式,事实上只有事一个名字,不是一个函数声明
函数实际上是绑定到变量f,不是g。

指定的标识符在函数表达式虽然有其用途:
堆栈跟踪是清晰而不是充斥着无名的函数,你可以有一个匿名函数递归调用本身不使用argument.callee

(function f(f){
    return typeof f();
        })(function(){ return 1; });

答案:”number”
为了便于理解我们继续分解;
第一部分:

var baz = function(){ return 1; };

第二部分:

(function f(f){
    return typeof f();
        })(baz);

在这里,函数f接受一个参数是另一个函数,f函数内部执行这个实参函数并且返回类型。
无论是从调用该函数返回,即使参数名称f与函数名冲突,函数接受本身作为自己的参数,然后调用,此时就看谁更具有更高的优先级了,显然,参数的优先级更高,所以实际执行的是return typeof 1。

var foo = {
    bar: function() { 
       return this.baz; },
       baz: 1    };    
(function(){
    return typeof arguments[0]();    
})
(foo.bar);

答案:”undefined”
为什么是”undefined”?
我们必须要知道this运算符是怎么工作的。
JS语言精粹总结的很精炼:
1 纯粹的函数调用
2 作为对象方法的调用
3 作为构造函数调用
4 apply调用
我们看看题目是属于那种环境?
在arguments0中执行了一个方法,arguments[0]就是foo.bar方法,注意:这在foo.bar中的this是没有绑定到foo。虽然 foo.bar 传递给了函数,但是真正执行的时候,函数 bar 的上下文环境是 arguments,并不是 foo。
arguemnts[0] 可以理解为 arguments.0(不过写代码就不要这样了,语法会错误的),所以这样看来,上下文环境是 arguemnts 就没问题了,所以在执行baz的时候自然this就是window了,window 上没有baz属性,返回的就是undefined,typeof调用的话就转换成”undefined”了。

事件的节流

// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
  // last为上一次触发回调的时间
  let last = 0
  // 将throttle处理结果当作函数返回
  return function () {
      // 保留调用时的this上下文
      let context = this
      // 保留调用时传入的参数
      let args = arguments
      // 记录本次触发回调的时间
      let now = +new Date() 
      // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
      if (now - last >= interval) {
      // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
          last = now;
          fn.apply(context, args);
      }
    }
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)

防抖

// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
  // 定时器
  let timer = null
  // 将debounce处理结果当作函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 每次事件被触发时,都去清除之前的旧定时器
    if(timer) {
        clearTimeout(timer)
    }
    // 设立新定时器
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('触发了滚动事件'), 1000)

document.addEventListener('scroll', better_scroll)

阿里JS面试题让你深入了解原型与继承
题目如下:

var F = function(){}
Object.prototype.a = function(){
console.log('a()')
}
Function.prototype.b = function(){
console.log('b()')
}
var f = new F()
F.a()
F.b()
f.a()
f.b()

主要考查的技术点:
1.、原型与原型链
2、实例对象、构造函数、Object、 Function的关系
分析:
F是个构造函数,而f是构造函数F的一个实例。
因为F instanceof Object == true、F instanceof Function == true
由此我们可以得出结论:F是Object 和 Function两个的实例,即F既能访问到a,也能访问到b。
所以F.a() 输出 a() F.b() 输出 b()
对于f,我们先来看下下面的结果:
f并不是Function的实例,因为它本来就不是构造函数,所以就调用Function原型链上的相关属性和方法了,只能访问Object原型链。
所以f.a() 输出 a() 而f.b()就报错了。
接下来,我们具体分析下,它们是如何按路径查找的:
f.a的查找路径: f自身: 没有 —> f.proto(Object.prototype): 输出a()
f.b的查找路径: f自身: 没有 —> f.proto(Object.prototype): 没有 —> f.proto.proto (Object.prototype.proto): 因为找不到,所以报错
F.a的查找路径: F自身: 没有 —> F.proto(Function.prototype): 没有 —> F.proto.proto(Object.prototype): 输出 a()
F.b的查找路径: F自身: 没有 —> F.proto(Function.prototype): b()

高阶函数实现AOP(面向切面编程)

Function.prototype.before = function (beforefn) {
        let _self = this; // 缓存原函数的引用
        return function () { // 代理函数
            beforefn.apply(this, arguments); // 执行前置函数
            return _self.apply(this, arguments); // 执行原函数
        }
    }

    Function.prototype.after = function (afterfn) {
        let _self = this;
        return function () {
            let set = _self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return set;
        }
    }

    let func = () => console.log('func');
    func = func.before(() => {
        console.log('===before===');
    }).after(() => {
        console.log('===after===');
    });

    func();

输出结果:
=before=
func
=after=

阶乘

const factorial1 = n => {
  if (n <= 1) return 1
  return n * factorial1(n - 1)
}

// 尾递归优化
const factorial2 = (n, total = 1) => {
  if (n <= 1) return total
  return factorial2(n - 1, total * n)
}

console.log(factorial1(3)) // 6
console.log(factorial2(3)) // 6
console.log(factorial1(5)) // 120
console.log(factorial2(5)) // 120

二分查找

//循环不变式 guess 等于l r中间位置
const bsearch = (A, x) => {
  let l = 0
  let r = A.length - 1
  let guess
  while (l <= r) {
    console.log('find')
    guess = Math.floor((l + r) / 2)
    if (A[guess] === x) return guess
    if (A[guess] > x) r = guess - 1
    else l = guess + 1
  }
  return -1
}

let arr = [1, 2, 3, 4, 5, 6, 7, 8]
console.log(bsearch(arr, 6)) // 5

Array push

// 类数组
let obj = {
  '1': 'a',
  '2': 'b',
  length: 2,
  push: Array.prototype.push
};

// Array.prototype.push.call(obj, 'c');
obj.push('c')

console.log(obj); // { '1': 'a', '2': 'c', length: 3 }

将两个有序数组合并为一个排好序的大数组

function mergeAry(left = [], right = []) {
  const result = [];
  while (left.length && right.length) {
    result.push(left[0] <= right[0] ? left.shift() : right.shift());
  }
  return result.concat(left, right);
}

console.log(mergeAry([1, 2, 3], [1, 4, 8, 9])); // [ 1, 1, 2, 3, 4, 8, 9 ]

面向对象以及JS运行机制
解法一

function CodingMan(name) { 
// 主要考察的是 面向对象以及JS运行机制(同步 异步 任务队列 事件循环)
	function Man(name) {
		setTimeout(() => { // 异步
			console.log(`Hi! This is ${name}`);
		}, 0);
	}
	Man.prototype.sleep = function(time) {
		let curTime = new Date();
		let delay = time * 1000;
		setTimeout(() => { // 异步
			while (new Date() - curTime < delay) {} // 阻塞当前主线程
			console.log(`Wake up after ${time}`);
		}, 0);
		return this;
	}
	Man.prototype.sleepFirst = function(time) {
		let curTime = new Date();
		let delay = time * 1000;
		while (new Date() - curTime < delay) {} // 阻塞当前主线程
		console.log(`Wake up after ${time}`);
		return this;
	}
	Man.prototype.eat = function(food) {
		setTimeout(() => { // 异步
			console.log(`Eat ${food}~~`);
		}, 0)
		return this;
	}
	return new Man(name);
}

// CodingMan('Peter');
// CodingMan('Peter').sleep(3).eat('dinner');
// CodingMan('Peter').eat('dinner').eat('supper');
// CodingMan('Peter').sleepFirst(5).eat('supper'); 

解法二

function CodingMan(name) {
    function fe() {
        fe.flag = true;
        console.log('Hi! This is ${name}');
    }
    fe.flag = false;
    fe.timer = setTimeout(() => {
        clearTimeout(fe.timer);
        if (!fe.flag) fe();
    }, 0);
    return fe;
}
Function.prototype.sleep = function (delay) {
    this()
    this.await(delay);
    return this;
}
Function.prototype.sleepFirst = function (delay) {
    this.await(delay);
    this();
    return this;
}
Function.prototype.eat = function (dinner) {
    setTimeout(() => {
        console.log('Eat ${dinner}~');
    });
    return this;
};
Function.prototype.await = function (delay) {
    delay = isNaN(delay) ? 0 : delay;
    let startTime = new Date();
    let delayTime = delay * 1000;
    while (new Date() - startTime <= delayTime) {
    }
    console.log('Wake up after ${delayTime}ms');
}
 // CodingMan('peter')
 // CodingMan('peter').sleep(2).eat('hanbao');
 // CodingMan('peter').sleepFirst(2).eat('hanbao');
 CodingMan('peter').eat('haha').eat('hanbao');

数组合并去重排序

let arr1 = [1, 25, 2, 26, 1234, 6, 213];
let arr2 = [2, 6, 2134, 6, 31, 623];
let c = [...new Set([...arr1, ...arr2])].sort((a, b) => {
	return a - b;
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值