# 二、前端基本功阶段

# 1、 以下 console.log(a) 打印的结果是什么?为什么打印出这个结果?

console.log(a) var a = 10;

答案:undefined,因为变量声明提升,会把 a 提升到 console.log()上面,上面的 a 的值是 undefined,所以打印 undefined

# 2、以下 console.log(a) 打印的结果是什么?为什么打印出这个结果?

var a = 10; function a() {} console.log(a)

答案:10,因为有函数声明提升,函数声明优先于变量声明提升,所以提升之后 functiona(){} 会在 var a 的前面去。所以打印 10。

# 3、什么是变量?

变量就是一个容器,可以存储不同类型的数据。

# 4、以下 console.log(a) 打印的结果是什么?为什么打印出这个结果(为什么不打印 undefined?)?

function a() {} console.log(a) var a = 10;

答案:ƒ a() {},原因:

function a() {}
console.log(a)
var a = 10;

以上代码,JS预解析如下:
function a(){}  // a 在这里已经声明了,a 是一个函数
var a; // a 在这里再次声明,重复声明的变量会被忽略,所以这里的代码不执行,
console.log(a) // 所以这里打印 ƒ a() {}
a = 10;


# 5、 null,undefined 的区别?

答案:

  • undefined 表示不存在这个值。
  • undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined
  • 例如变量被声明了,但没有赋值时,就等于undefined
  • null 表示一个对象被定义了,值为“空值”
  • null : 是一个对象(空对象, 没有任何属性和方法)
  • 例如作为函数的参数,表示该函数的参数不是对象;
  • 在验证null时,一定要使用 === ,因为 ==无法分别null 和 undefined

# 6、原始类型有哪几种?null 是对象吗?

答案:

在 JS 中,存在着 6 种原始值,分别是:

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol

首先原始类型存储的都是值,是没有函数可以调用的,比如undefined.toString()

虽然'1'.toString()是可以使用的。但是其实在这种情况下,'1'已经不是原始类型了,而是被强制转换成了String类型也就是对象类型,所以可以调用toString函数。

对于null来说,很多人会认为他是个对象类型,但其实这是错误的。虽然typeof null会输出object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000开头代表是对象,然而null表示为全零,所以将它错误的判断为object

# 7、 请解释什么是事件代理?

答案:

  • 事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是 DOM 元素的事件冒泡。使用事件代理的好处是可以提高性能
  • 可以大量节省内存占用,减少事件注册,比如在table上代理所有tdclick事件就非常棒
  • 可以实现当新增子对象时无需再次对其绑定

# 8、谈一谈闭包

答案:

  • 闭包就是能够读取其他函数内部变量的函数
  • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
  • 闭包的特性:
    • 函数内再嵌套函数
    • 内部函数可以引用外层的参数和变量
    • 参数和变量不会被垃圾回收机制回收

对闭包的理解

  • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在 js 中,函数即闭包,只有函数才会产生作用域的概念
  • 闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
  • 闭包的另一个用处,是封装对象的私有属性和私有方法
  • 好处:能够实现封装和缓存等;
  • 坏处:就是消耗内存、不正当使用会造成内存溢出的问题

使用闭包的注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露
  • 解决方法是,在退出函数之前,将不使用的局部变量全部删除

# 9、基本数据类型和引⽤类型在存储上的区别?

答案:

基本数据类型存储在栈上,引⽤类型存储在堆上

# 10、 谈一谈 let 与 var 的区别?

答案:

  • let命令不存在变量提升,如果在let前使用,会导致报错
  • 如果块区中存在letconst命令,就会形成封闭作用域
  • 不允许重复声明,因此,不能在函数内部重新声明参数

# 11、以下输出的结果是?为什么?

let c = { greeting: 'Hey!' }
let d

d = c
c.greeting = 'Hello'
console.log(d.greeting)

答案:

在 JavaScript 中,当设置两个对象彼此相等时,它们会通过引用进行交互。

首先,变量 c 的值是一个对象。接下来,我们给 d 分配了一个和 c 对象相同的引用。

因此当我们改变其中一个对象时,其实是改变了所有的对象。

# 12、call, apply 的相同点和不同点

答案:

  • callapply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。
  • 除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组
let a = {
  value: 1,
}
function getValue(name, age) {
  console.log(name)
  console.log(age)
  console.log(this.value)
}
getValue.call(a, '张三', '24')
getValue.apply(a, ['张三', '24'])

# 13、什么是 JavaScript 原型、原型链 ? 有什么特点?

答案:

  • 每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时
  • 如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念
  • 关系:instance.constructor.prototype = instance.__proto__
  • 特点:
    • JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
  • 当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的
  • 就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象
  • 原型:
    • JavaScript的所有对象中都包含了一个 [__proto__] 内部属性,这个属性所对应的就是该对象的原型
    • JavaScript 的函数对象,除了原型 [__proto__] 之外,还预置了 prototype 属性
    • 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [__proto__]
  • 原型链:
    • 当一个对象调用的属性/方法自身不存在时,就会去自己 [__proto__] 关联的前辈 prototype 对象上去找
    • 如果没找到,就会去该 prototype 原型 [__proto__] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
  • 原型特点:
    • JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变

# 14、Javascript 如何实现继承?

答案:

  • 构造继承
  • 原型继承
  • 类继承
  • ……

# 15、JavaScript 的组成?

答案:

由以下三部分组成:

  • ECMAScript(核心):JavaScript` 语言基础
  • DOM(文档对象模型):规定了访问HTMLXML的接口
  • BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法

# 16、 $(this) 和 this 关键字在 jQuery 中有何不同?

答案:

$(this)返回一个 jQuery对象,你可以对它调用多个 jQuery方法,比如用 text()获取文本,用val() 获取值等等。而 this代表当前元素,它是 JavaScript关键词中的一个,表示上下文中的当前DOM元素。你不能对它调用 jQuery方法,直到它被 $()函数包裹,例如 $(this)

# 17、== 和 ===有什么区别?

答案:

===叫做严格相等,是指:左右两边不仅值要相等,类型也要相等,例如'1'===1的结果是false,因为一边是string,另一边是`number

==不像===那样严格,对于一般情况,只要值相等,就返回 true,但==还涉及一些类型转换,它的转换规则如下**

  • 两边的类型是否相同,相同的话就比较值的大小,例如1==2,返回false
  • 判断的是否是nullundefined,是的话就返回 true
  • 判断的类型是否是StringNumber,是的话,把String类型转换成Number,再进行比较
  • 判断其中一方是否是Boolean,是的话就把Boolean转换成Number`,再进行比较
  • 如果其中一方为Object,且另一方为StringNumber或者Symbol,会将Object转换成字符串,再进行比较

# 18、根据如下代码,请问 console.log(a == b) 和 console.log(a === b) 打印的结果分别是什么?

var a = '42'
var b = a * 1
console.log(a == b)
console.log(a === b)

答案:

var a = '42'
var b = a * 1 // "42" 隐式转型成 42

a // "42"
b // 42 -- 是个数字!

console.log(a == b) // true   == 只判断值是否相等
console.log(a === b) // false   === 判断类型和值都要相等

# 19、new 的原理是什么?通过 new 的方式创建对象和通过字面量创建有什么区别?

调用new的过程中会发生以上四件事情:

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

根据以上几个过程,我们也可以试着来自己实现一个new

function create() {
  let obj = {}
  let Con = [].shift.call(arguments)
  obj.__proto__ = Con.prototype
  let result = Con.apply(obj, arguments)
  return result instanceof Object ? result : obj
}

以下是对实现的分析:

  • 创建一个空对象
  • 获取构造函数
  • 设置空对象的原型
  • 绑定this并执行构造函数
  • 确保返回值为对象

对于对象来说,其实都是通过new产生的,无论是function Foo()还是let a = { b : 1 }

对于创建一个对象来说,更推荐使用字面量的方式创建对象(无论性能上还是可读性)。因为你使用new Object()的方式创建对象需要通过作用域链一层层找到Object,但是你使用字面量的方式就没这个问题。

function Foo() {}
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 }
// 这个字面量内部也是使用了 new Object()

# 20、var、let 、 const 的区别?

答案:

  • var 存在提升,我们能在声明之前使用。letconst 不能在声明前使用
  • var 在全局作用域下声明变量会导致变量挂载在 window上,其他两者不会
  • letconst 作用基本一致,但是后者声明的变量不能再次赋值

# 21、什么是函数的形参和实参?

答案:

形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数。

形参就是函数声明时的变量, 实参是我们调用该函数时传入的具体参数

# 22、 jQuery 对象有什么特点?

答案:

  • 只有 JQuery对象才能使用 JQuery 方法
  • JQuery 对象是一个数组对象

# 23、以下代码,打印的结果是,为什么?

console.log(a)
var a = 10
function a() {}

答案:function a(){} ,原因如下:

以上代码,JS预解析如下:
function a(){}  // a 在这里已经声明了,a 是一个函数
var a; // a 在这里再次声明,重复声明的变量会被忽略,所以这里的代码不执行,
console.log(a) // 所以这里打印 ƒ a() {}
a = 10;

# 24、给定一个整数数组 nums 和一个整数目标值 target,请在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。例如:var nums = [2, 8, 7, 15] ,target = 9; nums[0] + nums[2] == 9。 返回 2 ,7 [0,2]

 	var nums = [28715];
    var target = 9;
    for (var i = 0; i < nums.length; i++) {
        for (var j = i + 1; j < nums.length; j++) {
            if (nums[i] + nums[j] == target) {
                console.log(nums[i], nums[j])
                console.log([i, j])
            }
        }
    }