JavaScript 前端面试题

176 阅读6分钟

1.call和apply的区别是什么,哪一个性能更好一些?

call和apply都是Function原型的方法,每一个函数作为Function的实例都可以调用这两个方法。都可以改变this指向。call和apply唯一的区别就是call传参数是一个一个传,apply是传一个数组,在数组里传入参数。

call性能要略好一些,尤其是参数超过三个的时候

2.实现(5).add(3).minus(2)

        Number.prototype.add = function(n){
            n = Number(n);  //提高容错率
            n = isNaN(n)? 0:n;
            return this + n; //返回的也得是个Number对象 这样才能链式调用
        }
        Number.prototype.minus = function(n){
            n = Number(n);
            n = isNaN(n)? 0:n;
            return this - n; //返回的也得是个Number对象 这样才能链式调用
        }
        console.log((5).minus(4).add(1));

3.箭头函数与普通函数有什么区别?构造函数可以使用new生成实例,那么箭头函数可以吗?为什么?

箭头函数与普通函数区别

1.箭头函数更加简洁

2.箭头函数没有自己的this,它里面出现的this从属于函数所处的上下文 (使用call、apply等都无法改变this指向)

3.箭头函数没有arguments

4.箭头函数不能被new执行(因为箭头函数没有this也没有prototype)

4.实现一个字符串匹配算法,从字符串S中查找是否存在字符串T,若存在,返回所在位置,否则返回-1.(不能基于indexOf,includes等内置方法)

String.prototype.myIndexOf = function (T){
    let lenT = T.length;
    let lenS = this.length;
    let res = -1;
    if (lenT > lens) return -1;
    for(let i= 0;i<lenS-lenT+1;i++){
        if(this.substr(i,lenT) === T){
            res = i;
            break;
        }
    }
    return res;
}
let S = 'haohaoxuexi'
let T = 'xue';
console.log(S.myIndexOf(T));

5.输出下面代码运行结果

//example1
var a = {}, b = '123', c=123;
a[b] = 'b';
a[c] = 'c';
console.log(a[b]);  // 'c'    对象中数字属性名和字符串属性名没区别  a['123']  <=> a[123]

//example 2
var a = {}, b = Symbol('123'), c=Symbol('123;);
a[b] = 'b';
a[c] = 'c';
console.log(a[b]);  // 'b'  Symbol创建出来的值是唯一值  所以Symbol('123') !== Symbol('123')//example3
var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';
a[c]='c';
console.log(a[b]); // 'c'  对象是不能作为另一个对象的属性名的,所以对象作为key会被转化为字符串。而对象
//的toString方法会把对象转化成'[Object Object]'  所以a里面是 {'[object Object]': "c"}
// obj = {}  arr=[12,23] obj[arr]='老王'   obj:{"12,23":"老王"}  Array的toString和Object的toString还有点区别

6.变态原型链

function Foo(){
    Foo.a = function () {
        console.log(1);
    }
    this.a = function() {
        console.log(2);
    }
}

//把Foo当做类,在原型上设置实例共有的方法  实例.a();
Foo.prototype.a = function() {
    console.log(3);
}

//把Foo当做普通对象设置私有的属性方法  Foo.a();
Foo.a = function () {
    console.log(4);
}
Foo.a();  //4
let obj = new Foo(); // 把Foo函数执行一遍,于是Foo.a被覆盖成输出1, 并且原型方法a也被覆盖成输出2
obj.a(); // 2
Foo.a(); //1

7.数组扁平化 编写一个程序 将数组扁平化,并去除其中重复部分数据,最终得到一个升序且不重复的数组

1.使用ES6中的Array.prototype.flat处理

arr = arr.flat(Infinity)
arr = Array.from(new Set(arr)).sort((a,b) =>a-b);

2.把数组直接变成字符串即可

arr = arr.toString().split(',').map(item => Number(item));
arr = Array.from(new Set(arr)).sort((a,b) =>a-b);

8.手写new

function Dog(name){
    this.name = name;
}
Dog.prototype.bark = function(){
    console.log('wangwang');
}
Dog.prototype.sayName = function(){
    console.log('my name is ' + this.name);
}

要求:基于内置的new关键词,我们可以创建dog的一个实例sanmao,实例可调用原型方法和属性
现在的需求是:自己实现一个_new方法 也能模拟内置的new后的结果

new之后要做的事情:

1.创建一个类的实例:创建一个空对象obj,将obj.__proto__设置为构造函数的prototype

2.初始化实例:构造函数被传入参数并调用,this指针被设定为指向该实例obj

3.返回实例obj

function _new(Fn,...arg){
    let obj = {};
    obj.__proto__ = Fn.prototype;
    Fn.call(obj,...arg);
    return obj;
}
let sanmao = _new(Dog,'三毛');

9.变量作用域

let fn = function AAA() {
    AAA = 1000;
    console.log(AAA);  //非严格模式下打印这个函数的内容  严格模式报错
};
AAA(); //Uncaught ReferenceError: AAA is not defined

//1.本该匿名的函数如果设置了函数名,在外面还是无法调用,但是在函数里面是可以使用的
//2.而且类似于创建常量一样,这个名字存储的值不能在修改。在非严格模式下不报错,但是不会有任何效果,严格模式直接报错
//我们可以把AAA理解成是用const创建的

所以:

var b = 10;
(function b() {
    b = 20;
    console.log(b); //输出函数本身
})();
console.log(b); //10

如果想让里面的b输出20 外面的b输出10,则里面的b就要是私有的(var、let、const声明或者在函数里加个形参b)

10.如何让 a==1 && a==2 && a==3同时成立?

==在进行比较的时候,如果两边数据类型不一样,则先转换为相同的数据类型

1.{} == {} 两个对象比较,比较的是堆内存的地址

2. null == undefined true  null === undefined false

3.NaN == NaN 不相等  NaN和谁都不相等

4.[12] == '12' 对象和字符串比较 是把对象toString()转换为字符串后再比较

5. 剩余的情况在比较时候 都是转换为数字(前提是数据类型不一样)

对象转数字:先转字符串再转数字

字符串转数字:只要出现一个非数字字符,结果就是NaN

布尔转数字:true--1  false -- 0

null 转数字 0

undefined 转数字 NaN

[12]==true    false

[] == false  true

[] == 1      false

true == 2    false

var a = ?
if(a == 1 && a ==2 && a == 3) {
    console.log(1);
}

  因为对象和数字比较 先把对象.toString(),而toString这个方法是定义在原型上的,我们可以定义在对象本身截胡这个toString,进而实现我们的目的

//方法一
var a = {
    n:0,
    toString: function(){
        return ++this.n;
    }
};

//方法二
let a = [1,2,3];
a.toString = a.shift;  //删除数组第一项 并且把删除的内容返回

11.对象调用push方法

let obj = {
    2:3,
    3:4,
    length: 2,
    push: Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);

//////////////////
{
    2: 1
    3: 2
    length: 4
    push: ƒ push()}

先看Array.prototype.push:(源码模拟)

Array.prototype.push = function (val) {
    this[this.length] = val;
    //this.length 自动+1  !!很重要
    return this.length
}

所以对象调用push方法的时候,

obj.push(1);  // this:obj  =>  obj[obj.length] = 1 => obj[2]=1  => obj.length = 3
obj.push(2);  //this:obj  => obj[obj.length] = 2  => obj[3]=2   =>obj.length = 4
所以输出的时候键值对被替换 length变成了4

12.冒泡排序

function bubble(ary) {
    let temp=null;
    for(var i=0;i<ary.length-1;i++){
        for(let j=0;j<ary.length-1-i;j++){
            if(ary[j] > ary[j+1]){
                temp = ary[j];
                ary[j] = ary[j+1];
                ary[j+1] = temp;
            }
        }
    }
return ary;
}

13.插入排序

function insert(arr){
    let handle = [];
    handle.push(arr[0]);
    for(var i=1;i<arr.length;i++){
        for(var j=handle.length-1;j>=0;j--){ //从后往前比 都可以
            if(arr[i] > handle[j]) {
                handle.splice(j+1,0,arr[i]);
                break;
            }
            if(j===0){ //上面那种情况之后 在这里判定的只会是arr[i]比handle开头的都要小
                handle.unshift(arr[i]);
            }
        }
    }
}

14. 快速排序

function quick(arr){
    if(arr.length<=1){
        return arr;
    }
    let midIndex=Math.floor(arr.length/2);
    let midValue = arr.splice(midIndex,1)[0];
    let arrLeft=[],arrRight=[];

    for(let i=0;i<arr.length;i++){
        let item=arr[i];
        item<midValue?arrLeft.push(item):arrRight.push(item);
    }
    return quick(arrLeft).concat(midValue,quick(arrRight));
}

15.对象转数组

let obj = {
    1:222,
    2:123,
    5:888,
} //这里的key代表月份 
请把数据处理为如下结构
[222,123,null,null,888,null,null,null,null,null,null,null]

数组一共表示十二个月 其中1 2 5月在对象中有值 请把对象转换成这个数组

//方法一
let arr = new Array(12).fill(null).map((item,index) => {
    return obj[index+1] || null;
});

//方法二
obj.length = 13; //加上length属性就可以调用Array.from方法了
let arr = Array.from(obj).splice(1).map(item => {
    return typeof item === "undefined" ? null : item;
})

//方法三
let arr = new Array(12).fill(null);
Object.keys(obj).forEach(item => {
    arr[item - 1] = obj[item];
});

16.旋转数组

function rotate(k) {
    if (k<0 || k === 0|| k === this.length) return this;
    if(k > this.length) k = k%this.length;
    return this.slice(-k).concat(this.slice(0,this.length-k));
}
Array.prototype.rotate = rotate;
let arr = [1,2,3,4,5,6,7];
arr.rotate(3);

17.手写map

关于为什么可以用Object.prototype.toString.call()判断 数据类型:

参考JavaScript:Object.prototype.toString方法的原理 - 知春里 - 博客园 (cnblogs.com)

Array.prototype.myMap = function(fn,context){
    if (Object.prototype.toString.call(fn) !== "[object Function]"){
        throw ('Error');
    }
    let resArray = [];
    let curArray = this;
    for( let i =0;i<curArray.length;i++){
        resArray[i] = fn.call(context, curArray[i], i,curArray);
    }
    return reaArray;
}
let arr = [3,4,5,6,7];
arr.myMap((item,index,arr) => {return item + 1})