前端面试题目整理

undefined 与null 的区别

null 空对象指针
不占内存,通俗理解就是人为的手动先赋值为 null ,后面程序中我们会再给它赋值为其他值

undefined 未定义
多数情况是某些浏览器内置机制设置的默认值,声明一个变量不赋值,这个变量的默认值就是 undefined

new 的过程

  1. 新生成了一个对象
  2. 链接到原型
  3. 绑定 this
  4. 返回新对象

在调用 new 的过程中会发生以上四件事情,我们也可以试着来自己实现一个 new

1
2
3
4
5
6
7
8
9
10
11
12
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}

关于this

this 是Javascript语言的一个关键字
this 对象是在运行时基于函数的执行环境绑定的(this要在执行时才能确认值,定义时无法确认)

this指向看的是

  1. 函数执行的时候谁调用的,那么函数中的this就是谁
  2. 函数执行前边无人调用,代表是全局window调用,自然函数中的this就是window
  3. 自执行函数/定时器/回调函数一般函数中的this指向window
  4. 存在call/apply/bind时,一般函数this指向会被修改,即函数执行时的第一个你指定的参数
    总之,一句话,并不看函数定义,只看函数执行,谁调用就是谁!

this几种不同使用场景
1.作为构造函数执行
2.作为普通函数执行
3.作为对象属性执行

  1. call,apply,bind
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // 作为构造函数运行
    function Foo(name){
    // this = {}
    this.name = name
    //return this
    }
    var f = new Foo('zhangsan')

    // 作为普通函数执行
    function fn(){
    console.log(this) // this === window
    }
    fn()

    // 作为对象属性执行
    var obj = {
    name : 'A',
    printName: function(){
    console.log(this.name)
    }
    }
    obj.printName() //this === obj

    // call,apply,bind
    // call 比较常用
    function fn1(name, age){
    alert(name)
    console.log(this)
    }
    fn1.call({x:100},'zhangsan',20)

    function fn2(name, age){
    alert(name)
    console.log(this)
    }
    fn2.apply({x:100},['zhangsan',20])

    var fn3 = function (name, age){
    alert(name)
    console.log(this)
    }.bind({y:200}) //注意用函数表达式
    fn3('zhangsan',20)

关于闭包

闭包就是能够读取其他函数内部变量的函数
闭包的2个使用场景:函数作为返回值,函数作为值传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 函数作为返回值
function fn(){
var a = 100

// 返回一个函数(函数作为返回值)
return function(){
console.log(a)//a是自由变量,父集作用域即定义的时候的作用域,父作用域中寻找
}
}
// fn 得到一个函数
var f1 = fn()
var a = 200
f1() //100

// 函数作为值传递
function fn1(){
var a = 100
return function(){
console.log(a)
}
}
var f1 = fn1()
function fn2(fn){
var a = 200
fn()
}
fn2(f1) //100

原型和原型链

原型本质上也是一个对象,其他对象可以通过它实现继承。

原型链指的是在JavaScript中对象之间的继承是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。

从输入 URL 到页面加载完成,发生了什么?

首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出我们的 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户,并时刻等待响应用户的操作(如下图所示)。

我们将这个过程切分为如下的过程片段:

  1. DNS 解析
  2. TCP 连接
  3. HTTP 请求抛出
  4. 服务端处理请求,HTTP 响应返回
  5. 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户

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

  6. Function.prototype.apply和Function.prototype.call 的作用是一样的,区别在于传入参数的不同
  7. 第一个参数都是,指定函数体内this的指向
  8. 第二个参数开始不同,apply是传入带下标的集合,数组或者类数组,apply把它传给函数作为参数,call从第二个开始传入的参数是不固定的,都会传给函数作为参数
  9. call比apply的性能要好,平常可以多用call, call传入参数的格式正是内部所需要的格式

输出以下代码的执行结果并解释为什么

1
2
3
4
5
6
var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x)
console.log(b.x)

解析:

1
2
3
4
5
6
var a = {n: 1};
var b = a;
a.x = a = {n: 2};

a.x // --> undefined
b.x // --> {n: 2}

答案已经写上面了,这道题最正的解释
这个需要结合JS引擎对堆内存和栈内存的管理来理解,即可一目了然。

1
var a = {n: 1};

这一行都明白,声明一个变量a,并为其赋值一个对象。

1
var b = a;

这一行也好理解, 创建一个变量b,为其赋值对象a。在栈内存中,a与b是不同的,是两个变量,但是他们的指针是相同的,指向同一个堆。

1
a.x = a = {n: 2};

这一行比较复杂。先获取等号左侧的a.x,但a.x并不存在,于是JS为(堆内存中的)对象创建一个新成员x,这个成员的初始值为undefined,

(这也是为什么直接引用一个未定义的变量会报错,但是直接引用一个对象的不存在的成员时,会返回undefined.)

创建完成后,目标指针已经指向了这个新成员x,并会先挂起,单等等号右侧的内容有结果了,便完成赋值。

接着执行赋值语句的右侧,发现a={n:2}是个简单的赋值操作,于是a的新值等于了{n:2}。

这里特别注意,这个a已经不是开头的那个a,而是一个全新的a,这个新a指针已经不是指向原来的值的那个堆内存,而是分配了一个新的堆内存。但是原来旧的堆内存因为还有b在占用,所以并未被回收。

然后,将这个新的对象a的堆内存指针,赋值给了刚才挂起的新成员x,此时,对象成员x便等于了新的对象a。

所以,现在b={n:1,x:{n:2}};a={n:2};a===b.x(true,注意对象的相等,不是值的相等,而是引用的相等,也就是说,相等表示指针是指向同一个堆内存。)

1
2
console.log(a.x);
console.log(b);

最后这就好理解了,a.x当然没有了,但是因为对象a存在,所以JS不会报错,a.x等于undefined

总结一下: 关键点一:a.x即完成了x的声明,其值为undefined。
关键点二:对象成员等待赋值时,锁定的赋值目标是成员,而非对象。
关键点三:对象重新赋值时,并非是修改原堆内存的值,而是重新分配堆内存,栈内存中的指针会做相应修改。(如果原堆内存有多个栈内存指向它,由于引用还存在,原堆内存的数据不会消失。如果堆内存再无其它引用,则会被JS的垃圾回收机制回收。对象的成员对象也一样。PS:引用类型应该都如此)