面试复习题-JavaScript基础篇

126 阅读6分钟

✊不积跬步,无以至千里;不积小流,无以成江海

JS数据类型有哪些?

一共八种。四基两空一对象+一大整数。

字符串、数字、布尔、undefined、null、大整数、符号、对象.

string、number、boolean、undefined、null、bigint、symbol、object.

->为什么会有bigint,因为number默认是双精度浮点数,但bigint则没有精度上的限制。

原型链是什么?

是什么

原型链涉及到的概念挺多的。首先我先举一个原型的例子吧。

假设我们有一个普通对象 x={} ,这个 x 会有一个隐藏属性,叫做 __?????__,这个属性会指向Object.prototype ,即

x.__?????__ === Object.prototype // 原型

此时,我们说x的原型是object.prototype;或者说object.prototype是x的原型。

而这个__?????__ 的唯一作用就是用来指向原型的。没有这个__?????__ x就会根本不知道自己的原型是谁。

接下来我们说一下原型链,还是用例子来说明。

假设我们有一个数组对象 a=[] ,这个 a 也会有一个隐藏属性,叫做 __?????__,这个属性会指向 Array.prototype ,即

Array.__?????__ === Array.prototype // 原型

此时,我们说 a 的原型是 Array.prototype ,跟上面的 x 一样。但又有一点不一样,那就是 Array.prototype 也有一个隐藏属性 __?????__,指向 Object.prototype ,即

Array.prototype.__?????__ === Object.prototype // 原型

这样一来,a 就有两层原型:

  1. a 的原型是 Array.prototype

  2. a 的原型的原型是 Object.prototype

于是就通过隐藏属性 __?????__形成了一个链条:

a ===> Array.prototype ===> Object.prototype

这就是原型链。

怎么做

看起来只要改写 x 的隐藏属性 __?????__ 就可以改变 x 的原型(链)

x.__?????__ = 我想要篡改成东西

但是这不是标准推荐的写法,推荐的写法是

const x = Object.create(我想要篡改成东西)//用object create
// 或
const x = new 构造函数() // 用 new,会令 x.__?????__ === 构造函数.prototype

这两种。

解决了什么问题

在没有 Class 的情况下实现「继承」。以 a ===> Array.prototype ===>Object.prototype 为例,我们说:

  1. a 是 Array 的实例,a 拥有 Array.prototype 里的属性

  2. Array 又继承了 Object

  3. a 是 Object 对象的间接实例,a 拥有 Object.prototype 对象里的属性

这样一来,a 就既拥有 Array.prototype 数组里的属性,又拥有 Object.prototype 对象里的属性。

优点:

简单、省事。

缺点:

跟 class 相比,不支持私有属性。

怎么解决缺点:

使用 class 呗。

这段代码中的 this 是多少?

var length = 4;

function callback() {

console.log(this.length); // => 打印出什么?

}

const obj = {

length: 5,

method(callback) {

callback();

}

};

obj.method(callback, 1, 2);

this实际上等价于

正常调用形式:

func.call(context, p1, p2)

其他两种都是语法糖,可以等价地变为 call 形式:

func(p1, p2) 等价于
func.call(undefined, p1, p2)

obj.child.method(p1, p2) 等价于
obj.child.method.call(obj.child, p1, p2)

如果没有传值,则this 就是 undefined。但浏览器给你一个默认的 this —— window 对象,所以打印出的 this 是 window。

解题思路

在这个题中,callback进行了一次传递(21行);一次参数(13行);一次调用(15行)。只需要关注最后一次执行时是什么,发现是(15行)。

则第15行可以被改写为,callback.call(undefine)。那么this.length = undefine.length;这种情况下应该打印出来的是window中的内容,即为window.length。

由于js的语法特性,全局变量自动变为window的属性。则var length = 4; 可以转换为window.length = 4.

因此this打印出来的是4.

JS 的 new 做了什么?

  1. 创建临时对象/新对象

  2. 绑定原型

  3. 指定 this = 临时对象

  4. 执行构造函数

  5. 返回临时对象

什么是立即执行函数

是什么:

声明一个匿名函数,然后立即执行它。这种做法就是立即执行函数。

怎么做:

//实际版的立即执行函数,function一个函数再调用这个函数:
fuction a(){
    console.log('hi')
}
a()
//去掉a后即为立即执行函数
//但因为这个值return的是undefined,所以没法[3].map这类的

(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来

(function(){alert('我是匿名函数')}) () // 用括号把函数包起来

!function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法

+function(){alert('我是匿名函数')}()

-function(){alert('我是匿名函数')}()

~function(){alert('我是匿名函数')}()

void function(){alert('我是匿名函数')}()

new function(){alert('我是匿名函数')}()

var x = function(){return '我是匿名函数'}()

解决了什么问题:

在 ES6 之前,只能通过它来「创建局部作用域」。

优点:

兼容性好。

缺点:

语法不美观。

如何解决缺点:

用es6的block+let。

什么是闭包

是什么

闭包是 JS 的一种语法特性。

闭包 = 函数 + 自由变量

怎么做

//声明一个立即执行函数,在立即执行函数中间声明一个变量,并在立即函数中调用这个变量
const add2 = function (){

    var count

    return function add (){ // 访问了外部变量的函数

    count += 1

}

}()

解决了什么问题:

  1. 避免污染全局环境。(因为用的是局部变量)

  2. 提供对局部变量的间接访问。(因为只能 count += 1 不能 count -= 1)

  3. 维持变量,使其不被垃圾回收。

优点:

简单,好用。

缺点:

闭包使用不当可能造成内存泄露。

怎么解决缺点:

慎用,少用,不用。

如何实现类

方法一:使用原型

//如果属性不能共享,就放在函数里面
function Dog(name){

    this.name = name

    this.legsNumber = 4

}

//如果能共享,就放在prototype里面
Dog.prototype.kind ='狗'

Dog.prototype.say = function(){console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)}

Dog.prototype.run = function(){console.log(`${this.legsNumber}条腿跑起来。`)}



const d1 = new Dog('啸天') // Dog 函数就是一个类

d1.say()

方法二:使用 class

class Dog {

kind = '狗' // 等价于在 constructor 里写 this.kind = '狗',代表他们是原型上的属性

//所有狗都有的属性放在这里
constructor(name) {

    this.name = name

    this.legsNumber = 4

    // 思考:kind 放在哪,放在哪都无法实现上面的一样的效果

}

//say & run 狗的共享属性

say(){console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}

run(){console.log(`${this.legsNumber}条腿跑起来。`)}

}

const d1 = new Dog('啸天')

d1.say()

如何实现继承

方法:使用原型链

继承的本质就是将两个类联合在一起。

首先定义两个类,animal类,有自身属性 & 共有属性;dog有自身属性以及一个原型say

function Animal(legsNumber){

    this.legsNumber = legsNumber //自身的

}

Animal.prototype.kind = '动物' //共有的

function Dog(name){

    this.name = name //自身的

    this.legsNumber = 4

}

Dog.prototype.say = function(){

    console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)

}

const d1 = new Dog('啸天') // Dog 函数就是一个类

console.dir(d1)

在dog中继承animal的自身属性,只需要加call.this。

如果dog想要继承animal的原型,也只需要让A原型的下划线prototype = B原型即可

...

function Dog(name){

    ...

    Animal.call(this, 4) // 调用一下animal,相当于实现了this.legsNumber = 4

}

Dog.prototype.__proto__= Animal.prototype

但由于下划线这种写法会被浏览器判定为具有误导性,所以要用新的写法来让dog能够继承animal的原型,方法如下:

...
var f = function(){ } //声明一个空的函数,这样空的部分就可以替代构造函数

f.prototype = Animal.prototype //则可与实现代替animal的prototype,做到了new的第三步

Dog.prototype = new f() //new一下执行new的五个操作,但第四个执行构造函数因为是空的所以其实相当于没有实行

补充:new的几个操作
1. 创建临时对象
2this = 临时对象
3. this._proto_ = 构造函数proto
4. 执行构造函数
5. return this

所以最终代码如下:

function Animal(legsNumber){

    this.legsNumber = legsNumber //自身的

}

Animal.prototype.kind = '动物' //共有的

function Dog(name){

    this.name = name //自身的

    Animal.call(this, 4) // 关键代码1,调用一下animal,相当于实现了this.legsNumber = 4

}

//Dog.prototype.__proto__= Animal.prototype // 关键代码2,狗的原型的原型=animal的原型,但容易被ban,如何解决在下面

var f = function(){ } //声明一个空的函数

f.prototype = Animal.prototype //代替animal的prototype

Dog.prototype = new f() //再new一下执行new的五个操作,但第四个执行构造函数因为是空的所以其实相当于没有实行

Dog.prototype.kind = '狗' //共有的

Dog.prototype.say = function(){

    console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)

}

const d1 = new Dog('啸天') // Dog 函数就是一个类

console.dir(d1)