# 二、前端基本功阶段
# 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
上代理所有td
的click
事件就非常棒 - 可以实现当新增子对象时无需再次对其绑定
# 8、谈一谈闭包
答案:
- 闭包就是能够读取其他函数内部变量的函数
- 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
- 闭包的特性:
- 函数内再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收
对闭包的理解
- 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在 js 中,函数即闭包,只有函数才会产生作用域的概念
- 闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
- 闭包的另一个用处,是封装对象的私有属性和私有方法
- 好处:能够实现封装和缓存等;
- 坏处:就是消耗内存、不正当使用会造成内存溢出的问题
使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露
- 解决方法是,在退出函数之前,将不使用的局部变量全部删除
# 9、基本数据类型和引⽤类型在存储上的区别?
答案:
基本数据类型存储在栈上,引⽤类型存储在堆上
# 10、 谈一谈 let 与 var 的区别?
答案:
let
命令不存在变量提升,如果在let
前使用,会导致报错- 如果块区中存在
let
和const
命令,就会形成封闭作用域 - 不允许重复声明,因此,不能在函数内部重新声明参数
# 11、以下输出的结果是?为什么?
let c = { greeting: 'Hey!' }
let d
d = c
c.greeting = 'Hello'
console.log(d.greeting)
答案:
在 JavaScript 中,当设置两个对象彼此相等时,它们会通过引用进行交互。
首先,变量 c
的值是一个对象。接下来,我们给 d
分配了一个和 c
对象相同的引用。
因此当我们改变其中一个对象时,其实是改变了所有的对象。
# 12、call, apply 的相同点和不同点
答案:
call
和apply
都是为了解决改变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
(文档对象模型):规定了访问HTML
和XML
的接口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
- 判断的是否是
null
和undefined
,是的话就返回 true - 判断的类型是否是
String
和Number
,是的话,把String
类型转换成Number
,再进行比较 - 判断其中一方是否是
Boolean
,是的话就把Boolean
转换成N
umber`,再进行比较 - 如果其中一方为
Object
,且另一方为String
、Number
或者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
的过程中会发生以上四件事情:
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 返回新对象
根据以上几个过程,我们也可以试着来自己实现一个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
存在提升,我们能在声明之前使用。let
、const
不能在声明前使用var
在全局作用域下声明变量会导致变量挂载在window
上,其他两者不会let
和const
作用基本一致,但是后者声明的变量不能再次赋值
# 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 = [2、 8、 7、 15];
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])
}
}
}