前端面试经典题型

1. let和var的区别:

题目:

 for(var i=0;i<3;i++){
        setTimeout(() => {
            console.log(i);
        }, 1);
  }
  for(let j=0;j<3;j++) {
        setTimeout(() => {
           console.log(j); 
        }, 1);
    }

答案:

333

012

解析:

由于JavaScript中的事件执行机制,setTimeout函数真正被执行时,循环已经走完。 由于第一个循环中的变量i是使用var关键字声明的,因此该值是全局的。在循环期间,我们每次使用一元运算符++都会将i的值增加1。 因此在第一个例子中,当调用setTimeout函数时,i已经被赋值为3。在第二个循环中,使用let关键字声明变量i:使用let(和const)关键字声明的变量是具有块作用域的(块是{}之间的任何东西)。 在每次迭代期间,i将被创建为一个新值,并且每个值都会存在于循环内的块级作用域。

2. 运算符的优先级:

题目:

var a = {
            n: 1
        }
var b = a;
a.n = a = {
            m: 2
        }
console.log(a) 
console.log('b', b) 

答案:

{m: 2}

b {n: {m:2}}

解析:

假设{n:1}存在1000的地址,先把{n:1}给b,那么b也指向1000地址,然后a.n就是先拿出1000地址的n,然后进行a={M:2},这个时候a不指向1000了,然后把这个a给到刚刚从1000地址拿出来的n,所以这个时候1000地址的对象变成了{n:{m:2}}。b是指向1000地址的,所以b是{n:{m:2}}。而a是{n:1};

3.obj与Set

题目:

const obj = {
            1: 'a',
            2: 'b',
            3: 'c'
        };
const set = new Set([1, 2, 3, 4, 5]);
console.log(obj.hasOwnProperty('1'));
console.log(obj.hasOwnProperty(1));
console.log(set.has('1'));
console.log(set.has(1))

答案:

true true false true

解析:

所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty('1')也返回true。上面的说法不适用于Set

在Set中没有“1”:set.has('1')返回false。它有数字类型1,set.has(1)返回true。

4. promise执行顺序

题目:

setTimeout(() => {
    console.log('tet1');
});
var p = new Promise((resolve, reject) => {
    resolve();
});
setTimeout(() => {
    console.log('tet2');
    new Promise(function(resolve, reject) {
        resolve();
    }).then(()=>{
        console.log('promise1');
    })
});

setTimeout(() => {
    console.log('set3');
});

p.then(function(){
    console.log('promise');
});
console.log(1234);

答案:

解析:

promise是微任务,setTime是宏任务2, js执行会先执行微任务,微任务队列空了, 再去看宏任务//同步执行完,先去执行微,所以先promise,微任务队列空了,去执行宏,所以接着set1,set2,set2里又加入一个微任务,微任务队列有了,就先做微任务,所以在是promise1,最后在set3

 

5.obj中的this指向

题目:

const shape = {
            radius: 10,
            diameter() {
                console.log(this.radius * 2);
            },
            per: () => {
                console.log(2 * Math.PI * this.radius);
            }
        }
shape.diameter();
shape.per();

答案:

20

NaN

解析:

diameter 中this 指向shape, this.radius * 2    => 20

per 中,this指向 windows,2*Math.PI * this.radius   => NaN

请注意,diameter是普通函数,而per是箭头函数。对于箭头函数,this关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用perimeter时,它不是指向shape对象,而是指其定义时的环境(window)。没有值radius属性,返回undefined。

6.立即执行函数

题目:

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

答案:

undefined

解析:

在arguments()中执行了一个方法,arguments[0]就是foo.bar方法,

arguments[0]()相当于arguments.0()(伪代码),

注意:这在foo.bar中的this是没有绑定到foo。

虽然 foo.bar 传递给了函数,但是真正执行的时候,

函数 bar 的上下文环境是 arguments,并不是 foo。

所以在执行this.baz的时候自然返回的就是undefined,typeof调用的话就转换成”undefined”了。

7.

题目:

var foo = {
    bar: function() {
        return this.baz;
    },
    baz: 1
}
f = foo.bar;
console.log(f());
console.log(f.call(foo));

答案:

undefined, 1

解析:

把foo.bar存储给f然后调用,

所以this在foo.bar引用的是全局对象,所以就没有baz属性了。

换句话说,foo.bar执行的时候上下文是 foo,

但是当 把 foo.bar 赋值给 f 的时候,

f 的上下文环境是 window ,是没有 baz 的,所以是 ”undefined”。

 

通过call使函数中的this指向 foo,因此为foo.baz

8. this在函数中的调用

题目:

var length = 10;
function fn() {
    console.log(this);
    console.log(this.length);
}

var obj = {
    length: 5,
    method: function(fn){
        console.log(this);// this => obj
        fn(); // this=> window
        arguments[0](); // this => Arguments
    }
}
obj.method(fn,1);

答案:

10,2

解析:

第一次输出10应该没有什么异议,这里的this指向window,

第二个调用arguments[0]()相当于执行arguments调用方法,this指向arguments,而这里传了两个参数,故输出arguments长度为2。

9.变量提升

题目:

var c = 1;
function c(c) {
    console.log(c);
    var c = 3;
}
c(2);

答案:

报错

解析:

首先声明函数c,再声明变量c时发现有同名属性,被忽略,接下来执行赋值语句 c=1,此时赋值成功,c成功转为一个变量。

当函数和变量都会被提升时,函数先被提升,然后才是变量。在处理变量声明时,若发现

作用域已有同名属性则忽略;但函数声明时会覆盖同名属性

var c = 1;
function fn(c) {
    console.log(c); // 2 形参相当于一个局部变量,已经定义且赋值
    var c = 3;
}
fn(2);

10.变量提升

题目:

var foo = {
    n: 1
};

function fun(foo) {
    var foo;
    console.log(foo.n); // 由于foo为对象引用,因此输入原来的值
    foo.n = 3; // 将全局的 foo.n的值修改
    foo = { // 局部新建关系,之前的引用断开
        n: 2
    };
    console.log(foo.n); // 2
};
fun(foo);
console.log(foo.n); // 3

11.不同数据类型间的加法

题目:

var a = 10;
a.pro = 10;
console.log(a); // 10
console.log(a.pro); // undefined
console.log(a.pro + a); //NaN

var s = 'hello';
s.pro = 'world';
console.log(s); // hello
console.log(s.pro); // undefined
console.log(s.pro + s); // undefinedhello

12.函数参数的个数

题目:

function test(x,y,z) {
    console.log(test.length); // 3
    console.log(arguments.length); //2
    console.log(arguments.callee === test); // true
    console.log(arguments[2]) // undefined
                        // arguments.callee  指向arguments对象所在的函数
}
test(10,20)

解析:

function.length : 获取形参的个数

arguments.length: 获取实参的个数

13.reduce的用法

题目:

console.log([[0,1],[2,3]].reduce((acc,cur) => {
    return acc.concat(cur);
})) // [0,1,2,3]

答案:

[1, 2, 0, 1, 2, 3]

解析:

其实就是考察reduce用法,

[1,2]是初始值。 这是我们开始执行reduce函数的初始值,以及第一个acc的值。

在第一轮中,acc是[1,2],cur是[0,1]。 我们将它们连接起来,结果是[1,2,0,1]。

然后,acc的值为[1,2,0,1],cur的值为[2,3]。

我们将它们连接起来,得到[1,2,0,1,2,3]。

14.数组去深度,去重,排序

题目:

// 一直如下数组:
// 编写一个程序将数组扁平化并除去其中重复部分的函数,最终得到一个升序且不重复的数组
var arr = [[11,22,22],[13,14,15,15],[16,17,18,19,[11,12,[12,13,[14]]],12]];

答案:

解法一:利用了join函数

function sortArray(arr) {
    let array = arr.join(',').split(',');
    array = array.map(el => +el);
    array = array.sort(function(x,y){
        if(x>y) return 1;
        if(x<y) return -1;
        return 0;
    })
    let result = [];
    for(let i = 0;i<array.length;i++){
       result.indexOf(array[i]) === -1 && result.push(array[i]);
    }
    return result;
}
console.log(sortArray(arr));

解法二:

var arr = [
[11,22,22],
[13,14,15,15],
[16,17,18,19,[11,12,[12,13,[14]]],12]
];

function sortArray(arr) {
    let result = [];
    arr = mergeArray(arr);
    if(!arr.length) {
        return arr;
    }
    arr.map(el => {
        result.indexOf(el) === -1 && result.push(el);
    });
    result = result.sort((x,y) =>{
        x>y && return 1;
        x<y && return -1;
        return 0;
    })
    return result;
}
function mergeArray(arr) {
    let result = [];
    let needMerge = false;
    if(!arr.length) {
        return arr;
    }else {
        arr.map(el => {
            (toString.call(el) === '[object Array]') ? result.push(...el) : result.push(el);
        })
    }
    result.map(el => {
        if(toString.call(el) === '[object Array]') {
            needMerge = true;
        }
    })
    if(needMerge) {
        return mergeArray(result);
    } else {
    return result;
    }
}
console.log(sortArray(arr))

答案三:

var arr = [
[11,22,22],
[13,14,15,15],
[16,17,18,19,[11,12,[12,13,[14]]],12]
];

// flat 方法接受一个参数n,可以将深度为n的数组扁平化,然后去重并排序,去重有set,排序用sort
// 当然也可以用递归等其他方法
var newArray = Array.from(new Set(arr.flat(Infinity))).sort((a,b) => a-b);
console.log(newArray);

// arr3.flat(Infinity) 使用 Infinity 作为深度,展开任意深度的嵌套数组
// Array.from(new Set(arr)) // 去重

15.通过函数实现基本数据类型的复制

题目:

// 实现一个函数clone,可以对JavaScript中的5中主要的数据类型
//(包括Number, String, Object, Array, Boolean) 进行值复制

答案:

function clone(obj) {
    // 非复杂类型,直接拷贝
    if(obj === null || obj === undefined || typeof obj !== 'object') {
        return obj;
    }
    var con = obj.constructor;
    // data和正则类型比较特殊
    if(con === Date) {return new Date(obj)};
    if(con === RegExp) {return new RegExp(obj)};
    // 区分Array
    var o = con === Array ? [] : {};
    for(var e in obj) {
        o[e] = typeof obj[e] === 'object' ? obj[e].clone() : obj[e];
    }
    return o;
}

16. 修改对象原型

题目:

String.prototype.giveLydiaPizza = () => {
return 'give Lydia pizza!'
}
const name = 'Tom';
console.log(name.giveLydiaPizza());

答案:

give Lydia pizza!

解析:

String是一个内置的构造函数,我们可以为它添加属性。

我刚给它的原型添加了一个方法。

原始类型的字符串自动转换为字符串对象,由字符串原型函数生成。

因此,所有字符串(字符串对象)都可以访问该方法!

当使用基本类型的字符串调用giveLydiaPizza时,实际上发生了下面的过程:

1、创建一个String的包装类型实例

2、在实例上调用substring方法

3、销毁实例

17.

题目:

var a = ?;
if(a == 1&&a==2&&a==3) {
    console.log('hello');
}
// 什么条件 输出'hello'

答案:

var a;
a = {
    index:1,
    valueOf:function() {
        return this.index++;
    }
}
if(a == 1&&a==2&&a==3) {
    console.log('hello');
}

18.报错执行顺序

题目:

(() => {
    let x,y;
    try{
        throw new Error();
    } catch (x) {
        (x =1),(y=2);
        console.log(x); // 1
    }
    console.log(x); // undefined
    console.log(y); // 2

})();

答案:

1,undefined,2

解析:

try 里面抛异常,直接进入catch,catch的回调参数为x,但又在catch的作用域里面将x赋值为1,y赋值为2,所以第一个console的值是1,第三个console的值是2;因为在catch的作用域内只对catch的回调参数x进行赋值,与上层定义的x无关,所以第二个console的值为undefined 

19.new 方法,与非new 方法的区别

题目:

function Person(firstName,lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}
const lydia = new Person('Lydia', 'Hallie');
const sarah = Person('Sarah', 'Smith');

console.log(lydia);
console.log(sarah);
console.log(this.firstName);
console.log(this.lastName)

答案:

Person {firstName: "Lydia", lastName: "Hallie"}
undefined
Sarah
Smith

解析:

对于sarah,我们没有使用new关键字。 使用new时,它指的是我们创建的新空对象。 但是,如果你不添加new它指的是全局对象!我们指定了this.firstName等于'Sarah和this.lastName等于Smith。 我们实际做的是定义global.firstName ='Sarah'和global.lastName ='Smith。 sarah本身的返回值是undefined因此this.firstName和this.lastName分别是Sarah和Smith了

20.作用域,原型链

题目:

function Foo() {
    getName = function () {
        console.log(1);
    };
    console.log(this)
    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();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

答案:

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_陌默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值