前端常见题汇总(一)

一、JS找字符串中出现最多的字符

例如:求字符串’nininihaoa’中出现次数最多字符

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

结果如下图
在这里插入图片描述

方法2
var arrString = 'abcdaabc';

arrString.split('').reduce(function(res, cur) {
    //若第一次出现,次数记为1, 否则 次数加1
    res[cur] ? res[cur] ++ : res[cur] = 1
    return res;
}, {})

结果如下图
在这里插入图片描述

备注:reduce讲解

reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

语法:

arr.reduce(callback,[initialValue])
例如

var items = [10, 120, 1000];

// our reducer function
var reducer = function add(sumSoFar, item) { return sumSoFar + item; };

// do the job
var total = items.reduce(reducer, 0);

console.log(total); // 1130

结果如图
在这里插入图片描述

二、实现累加

方法1

楼上的reduce方法实现累加

方法2
function add(a) {
    function sum(b) { // 使用闭包
     a = a + b; // 累加
     return sum;
    }
    sum.toString = function() { // 重写toString()方法
        return a;
    }
    return sum; // 返回一个函数
}

add(1)(2)(3)(4) // 10

结果如下图
在这里插入图片描述

备注 toString讲解

1.toString()可以将所有的的数据都转换为字符串,但是要排除null 和 undefined

例如 将false转为字符串类型

var str = false.toString();
console.log(str, typeof str);
//  结果为 false,string

null 和 undefined转换为字符串时会报错

var str = null.toString();
console.log(str, typeof str);
//结果程序报错
var str = undefined.toString();
console.log(str, typeof str);
//结果程序报错

在这里插入图片描述
但是 string() 可以
在这里插入图片描述

2.toString() 括号中的可以写一个数字,代表进制,对应进制字符串

二进制:.toString(2);

八进制:.toString(8);

十进制:.toString(10);

十六进制:.toString(16);

三、作用域和闭包

题目:现在有个 HTML 片段,要求编写代码,点击编号为几的链接就alert弹出其编号

  • 编号1,点击我请弹出1
  • 2
  • 3
  • 4
  • 5

一般不知道这个题目用闭包的话,会写出下面的代码:

var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
    list[i].addEventListener('click', function(){
        alert(i + 1)
    }, true)
}

实际上执行才会发现始终弹出的是6,这时候就应该通过以下几种:

方法1:for循环【let作用域】
for (let i = 0; i < list.length; i++) {

	   list[i].addEventListener('click', function(){
			alert(i + 1)
	   }, true)
	}

方法2: each循环
$.each(list,function(i,v){
	
		list[i].addEventListener('click', function(){
           alert(i + 1)
      }, true)
	})

或者下图所示
在这里插入图片描述

方法3: 闭包
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
    list[i].addEventListener('click', function(i){
        return function(){
            alert(i + 1)
        }
    }(i), true)
}
备注: 作用域和闭包

要理解闭包,就需要我们从「执行上下文」开始讲起。

先讲一个关于变量提升的知识点,面试中可能会遇见下面的问题,很多候选人都回答错误:

题目:说出下面执行的结果(这里笔者直接注释输出了)

console.log(a)  // undefined
var a = 100

fn('zhangsan')  // 'zhangsan' 20
function fn(name) {
    age = 20
    console.log(name, age)
    var age
}

console.log(b); // 这里报错
// Uncaught ReferenceError: b is not defined
b = 100;

在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的(内部函数的不算,因为你不知道函数何时执行)变量、函数声明都拿出来。变量先暂时赋值为undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。再次强调,这是在代码执行之前才开始的工作。

我们来看下上面的面试小题目,为什么a是undefined,而b却报错了,实际 JS 在代码执行之前,要「全文解析」,发现var a,知道有个a的变量,存入了执行上下文,而b没有找到var关键字,这时候没有在执行上下文提前「占位」,所以代码执行的时候,提前报到的a是有记录的,只不过值暂时还没有赋值,即为undefined,而b在执行上下文没有找到,自然会报错(没有找到b的引用)。

另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过函数执行上下文中会多出this``arguments和函数的参数。参数和arguments好理解,这里的this咱们需要专门讲解。

总结一下:

var a = {
    name: 'A',
    fn: function () {
        console.log(this.name)
    }
}
a.fn()  // this === a
a.fn.call({name: 'B'})  // this === {name: 'B'}
var fn1 = a.fn
fn1()  // this === window

this执行会有不同,主要集中在这几个场景中

作为构造函数执行,构造函数中
作为对象属性执行,上述代码中a.fn()
作为普通函数执行,上述代码中fn1()
用于call、apply、bind,上述代码中a.fn.call({name: ‘B’})
下面再来讲解下什么是作用域和作用域链,作用域链和作用域也是常考的题目。

题目:如何理解 JS 的作用域和作用域链

作用域
ES6 之前 JS 没有块级作用域。例如

if (true) {
    var name = 'zhangsan'
}
console.log(name)

从上面的例子可以体会到作用域的概念,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。上面的name就被暴露出去了,因此,JS 没有块级作用域,只有全局作用域和函数作用域

var a = 100
function fn() {
    var a = 200
    console.log('fn', a)
}
console.log('global', a)
fn()

全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。

// 张三写的代码中
var data = {a: 100}

// 李四写的代码中
var data = {x: true}

这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。

附:ES6 中开始加入了块级作用域,使用let定义变量即可,如下:

if (true) {
    let name = 'zhangsan'
}
console.log(name)  // 报错,因为let定义的name是在if这个块级作用域

作用域链
如下代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,这成为自由变量。自由变量如何得到 —— 向父级作用域寻找。

var a = 100
function fn() {
    var b = 200
    console.log(a)
    console.log(b)
}
fn()

如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链

var a = 100
function F1() {
    var b = 200
    function F2() {
        var c = 300
        console.log(a) // 自由变量,顺作用域链向父作用域找
        console.log(b) // 自由变量,顺作用域链向父作用域找
        console.log(c) // 本作用域的变量
    }
    F2()
}
F1()

闭包
讲完这些内容,我们再来看一个例子,通过例子来理解闭包。

function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
var f1 = F1()
var a = 200
f1()

自由变量将从作用域链中去寻找,但是依据的是函数定义时的作用域链,而不是函数执行时,以上这个例子就是闭包。闭包主要有两个应用场景:

函数作为返回值,上面的例子就是
函数作为参数传递,看以下例子

function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1)

在这里插入图片描述
如下:
在这里插入图片描述
闭包的特点:
在这里插入图片描述
在这里插入图片描述

至此,对应着「作用域和闭包」这部分一开始的点击弹出alert的代码再看闭包,就很好理解了。
闭包的几种形式,请看链接
https://www.cnblogs.com/xiaohuochai/p/6834565.html.
以及IIFE(立即执行函数)
https://www.cnblogs.com/xiaohuochai/p/5731016.html#anchor4.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值