前言
听从爸爸的劝说,昨晚试着在实习僧上找找附近有没有公司提供实习的机会.
结果发现两家觉得很不错,一家是趣头条,另一家是哔哩哔哩.
趣头条这家公司我只是很早之前听说过, 说实话这名字取得真不太好, 起得跟个垃圾软件一样. 不过后来查了下, 17 年左右已经在纳斯达克上市了, 应该也不会差的.
在看准网上查了下之前员工的一个评价, 人员流动性大 、 公司内部斗争严重 、加班多 看来前员工对这家公司的评价倒是不太好.
最让我觉得有意思的是知乎上的一个评论, 说趣头条做聚合类新闻, 说自己是第二, 想想也没有问题. 第一是今日头条, 第二是趣头条, 第三是一点资讯. 可是这个类别里老二 + 老三 的份额乘以四都赶不上老大.
份额大小其实对一个实习生而言无差, 如果有机会去这种大公司里实习, 相信一定也能学到很多东西.
第二家公司则是哔哩哔哩, 这个不多说, 再熟悉不过. 昨晚花了一个多小时删改之前的简历, 晚上 11 点把简历发了过去, 现在正在等待消息.
这篇文章《 JavaScript 基础面试题(一) 》 也是写于这个情况之下, 可能收到面试的通知, 所以我需要提前做点准备, 毕竟在 JavaScript 这一块理解不够深入, 一些简单的逻辑都可以实现, 但是如果问些原理, 那就是一问三不知了.
本来现在还在按照计划学习 Vue 框架, 正准备开始做去哪儿仿站. 现在我不得不改变一下计划, 把原本准备放在最后的基础知识拿到现在来看.
现在做的这些努力也只是为了弥补大学前两年的荒废, 用一句话安慰自己 “尽人事,听天命”.
正文
重点知识点
基础知识:
- 原型 原型链
- 作用域 闭包
- 异步 单线程
JS API:
- DOM 操作
- AJAX
- 时间不定
开发环境:
- 版本管理
- 模块化
- 打包工具
运行环境:
- 页面渲染
- 性能优化
变量类型和计算
知识点
- 变量类型
- 值类型与引用类型
- typeof 运算符详解
- 变量运算(值类型)
- 字符串拼接
- == 运算符
1
2
3
4
100 == '100' //true
0 == '' //true
null== undefined //true
// 慎用 == , 尽可能使用 ===
- if 语句
- 逻辑运算
JS 中使用 typeof 能得到哪些类型?
1
2
3
4
5
6
7
8
9
typeof undefined //undefined
typeof 'abc' //string
typeof 123 //number
typeof true //boolean
typeof console.log //function
typeof {} //object
typeof [] //object
typeof null //object
// 1 2 3 4 行为值类型, 5 6 7 8 为引用类型
何时使用 === 何时使用 ==
1
2
3
if (obj.a == null) {
// 这里的 'obj.a == null' 相当于 'obj.a === null || obj.a === undefined', 简写形式, 这是 JQuery 源码中的推荐写法.
}
除了以上的情况外, 其他均使用 ===
JS 中有哪些内置函数?
1
2
3
4
5
6
7
8
9
Object
Array
Boolean
Number
String
Function
Date
RegExp //正则表达式
Error
JS 变量按照存储方式区分为哪些类型, 并描述特点
分为: 值类型 与 引用类型
如何理解 JSON ?
本质上是一个对象, 也是一种数据格式.
原型和原型链
知识点
- 构造函数
- 构造函数拓展
var a = {}等于var a = new Object()function Foo(){}等于var a = new Function('...')
- 原型规则和示例
- 除
null外,所有的引用类型(数组/对象/函数), 都具有对象特性, 即可以自由拓展属性 - 所有的引用类型(
数组/对象/函数), 都有一个__proto__(隐式原型)属性, 属性值是一个普通的对象 - 所有函数都有
prototype(显式原型)属性, 属性值是一个普通对象 - 所有的引用类型(
数组/对象/函数),__proto__属性值指向它的构造函数的prototype属性值 - 如果对象本身没有该属性, 那么会去它的
__proto__中寻找.
- 除
- 原型链

- instanceof
- 用于判断
引用类型属于哪个构造函数的方法 - 通过对象的
__proto__一层层往上, 看能否找到对应的构造函数的prototype
- 用于判断
如何准确判断一个变量是不是数组类型?
1
2
3
var arr = [];
arr instanceof Array //true
typeof arr //object typeof 是无法判断变量是否为数组的
写出一个原型链继承的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动物
function Animal() {
this.eat = function () {
console.log('animal eat');
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dog bark');
}
}
Dog.prototype = new Animal();
// 哈士奇
var haShiQi = new Dog();
描述 new 一个对象的过程
- 创建一个新对象
this指向这个新对象- 执行代码, 即对
this赋值 - 返回
this
1
2
3
4
5
6
7
8
9
function Foo(name, age) {
this.name = name;
this.age = age;
this.class = 'class-1';
// return this 这一行这是默认有的
var 发= new Foo('Tom', 20);
// 在实例化的时候先生成一个空对象,然后根据传入的参数对属性赋值, 再通过 return this 返回出来.
}
作用域和闭包
JavaScript 是解释型语言, 所以有些错误只有在执行时才会报错.
知识点
- 执行上下文
- 范围: 一段
<script>或一个函数之内都会生成上下文 - 全局: 变量定义 / 函数声明 / 一段
<script> - 函数: 变量定义 / 函数声明 /
this/arguments(函数执行前会生成函数上下文) - 注意:
函数声明和函数表达式的区别
- 范围: 一段
this- 只有在执行时才能确认值,定义时无法确认
1
2
3
4
5
6
7
var a = {
name: "A",
fn: function() {
console.log(this.name);
}
};
a.fn(); //输出为 A, 此时 this 为 a
1
2
3
4
5
6
7
var a = {
name: "A",
fn: function() {
console.log(this.name);
}
};
a.fn.call({name:'B'}); //输出为: B, 此时 this 为 {name:'B'}
1
2
3
4
5
6
7
8
9
var a = {
name: "A",
fn: function() {
console.log(this.name);
}
};
var fn1 = a.fn;
fn1(); //输出为: undefined, 此时 this 为 window
- 作用域
- JavaScript 中没有块级作用域, 只有
函数和全局作用域
- JavaScript 中没有块级作用域, 只有
- 作用域链
在当前作用域如果找不到使用的变量, 则会往上一级父作用域寻找, 如果找不到则会一直找到全局作用域.
注意: 函数的父级作用域是函数定义时候的作用域,不是函数执行时候的作用域. 也就是说哪个作用域定义了这个函数,这个函数的父级作用域就是谁,跟函数执行没有关系,函数自由变量要到父级作用域中找,就形成了作用域链

- 闭包
- 闭包使用场景
- 函数作为返回值
- 函数作为参数传递
- 闭包使用场景
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
// 1.函数作为返回值
function F1(){
var a = 100;
// 返回一个函数
return function () {
console.log(a);
}
}
// 此时 f1 得到一个函数
var f1 = F1();
var a = 200;
f1(); //注意: 此时输出值为: 100
// 两个 a 完全不一样, 因为作用域不同. 是两个不相关的变量
// 2.函数作为参数传递
function F1(){
var a = 100;
// 返回一个函数
return function () {
console.log(a);
}
}
var f1 = F1();
function F2(fn) {
var a = 200;
fn();
}
F2(f1); //此时输出的还是 100, 因为定义时的作用域中 a = 100.
函数的父级作用域是函数定义时候的作用域,不是函数执行时候的作用域.
说一下变量提升的理解
两种情况会出现变量提升:
- 变量的定义
- 函数的声明(注意和函数表达式的区别)
说明this几种不同的使用场景
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
// 作为构造函数执行
function Foo(name) {
this.name = name;
}
var f = new Foo('zhangsan');
// 作为对象属性执行
var obj = {
name: 'A',
printName: function () {
console.log(this.name);
}
}
obj.printName();
// 作为普通函数执行
function fn() {
console.log(this);
}
fn(); //此时 this === window
// call apply bind
function fncall(name,age) {
alert(name + age);
console.log(this) //此时 this === window
}
fncall.call({x:100},'Tom',20);
// 此时弹出: Tom20, 输出: {x:100}
// apply 与 call 作用类似, 只不过 apply 的参数是以数组形式传进对象的.
var fnbind = function (name, age) {
alert(name + age);
console.log(this)
}.bind({y:200})
fnbind('John',15);
// 此时弹出: John15, 输出{y:200}
创建10个<a>标签, 点击的时候弹出来对应的序号
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
// 错误写法
var i, a;
for (i = 0; i < 10; i++) {
var a = document.createElement('a');
a.innerHTML = i + '<br>';
a.addEventListener('click', function (e) {
e.preventDefault();
alert(i);
}
document.body.appendChild(a);
}
// 这样写确实能够创建 10 个 a 标签, 但是当你点击任何一个标签时, 弹出的结果都是 10. 因为他的向外找 i 时, i 已经全部循环完毕变成10.
// 正确写法
var i;
for (i = 0; i < 10; i++) {
(function(i){
var a = document.createElement('a');
a.innerHTML = i + '<br>';
a.addEventListener('click', function (e) {
e.preventDefault();
alert(i);
})
document.body.appendChild(a);
})(i)
}
// 在原本的方法外包裹一层 function, 将 i 传入, 这样就可以避免上面的问题.
如何理解作用域
- 自由变量
- 作用域链, 即自由变量的查找
- 闭包的两个场景
实际开发中闭包的应用
闭包实际应用主要用于封装变量, 收敛权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function isFirstLoad() {
var _list = [];
return function (user_id) {
if (_list.indexOf(user_id) >= 0) {
return false;
} else {
_list.push(user.id);
return true;
}
}
}
//使用
var firstLoad = isFirstLoad();
firstLoad(001) //true
firstLoad(001) //false
firstLoad(002) //true
// 这样在 isFirstLoad 外, 根本不可能修改 _list 的值.
异步和单线程
使用异步的场景:
- 定时任务:
setTimeout,setInverval - 网络请求:
ajax请求, 动态<img>加载 - 事件绑定
1
2
3
4
5
6
7
// 事件绑定
console.log('start');
document.getElementById('btn1').addEventListener('click', function () {
alert('click');
})
console.log('end');
// 事件绑定需要使用异步, 因为不可能你不触发这个事件其他代码就 不执行了.
同步与异步的区别
- 同步会阻塞代码执行, 异步不会
setTimeout 笔试题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(1);
setTimeout(function () {
// setTimeout 不管延迟多少时间都会被暂存起来, 知道其他执行结束后拿出来执行.
console.log(2);
},0);
console.log(3);
setTimeout(function () {
console.log(4);
},1000);
console.log(5);
输出结果为:
1
1 3 5 2 4
其他知识
知识点
- 日期
1
2
3
4
5
6
7
8
9
Date.now(); //获取当前日期毫秒数
var dt = new Date();
dt.getTime(); //获取毫秒数
dt.getFullYear();
dt.getMonth(); //月 (0-11)
dt.getDate(); //日 (0-31)
dt.getHours(); //小时 (0-23)
dt.getMinutes(); //分 (0-59)
dt.getSeconds(); //秒 (0-59)
- Math
- 获取随机数 Math.random()
- 数组 API
- forEach 遍历所有元素
1
2
3
4
5
var arr = [1,2,3];
arr.forEach(function (item,index) {
// 遍历数组所有元素
console.log(index, item);
})
- every 判断所有元素是否都符合条件
1
2
3
4
5
6
7
var arr = [1,2,3];
var result = arr.every(function (item, index) {
if (item < 4) {
return true;
}
})
console.log(result);
- some 判断是否至少有一个符合
- sort 排序
1
2
3
4
5
6
7
8
var arr = [1,2,5,4,3];
var arr2 = arr.sort(function(a, b) {
// 从小到大
return a - b;
// 从大到小
return b - a;
})
console.log(arr2);
- map 对元素重新组装, 生成新数组
1
2
3
4
5
6
var arr = [1,2,3,4];
var arr2 = arr.map(function(item, index) {
// 将元素重新组装, 并返回
return item + 1;
})
console.log(arr2);
- filter 过滤符合条件的元素
1
2
3
4
5
6
7
8
var arr = [1,2,3];
var arr2 = arr.filter(function (item, index) {
// 需要什么数, 则令他返回 true
if (item > 2) {
return true;
}
})
console.log(arr2);
- 对象 API
1
2
3
4
5
6
7
8
var obj = {
x: 100,
y: 200
}
for (var key in obj) {
console.log(key, obj[key]);
}
// 输出: x 100 y 200
获取 2017-06-10 格式的日期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function formatDate(dt) {
if (!dt) {
dt = new Date();
}
var year = dt.getFullYear();
var month = dt.getMonth();
var date = dt. getDate();
if (month < 10) {
month = '0' + month;
}
if (date < 10) {
date = '0' + date;
}
return year + '-' + month + '-' + date;
}
var dt = new Date();
var formatDate = formatDate(dt);
console.log(formatDate);
获取随机数, 要求长度一致的字符串格式
1
2
3
4
5
// 使随机数长度都为 3
var random = Math.random();
random = random + '000'; //在数字末尾加 3 个零
random = random.slice(0, 3);
console.log(random);
先给随机数添加末尾是为了避免长度不够的情况.
写一个能遍历对象和数组的通用 forEach 函数
1
2
3
4
5
6
7
8
9
10
11
12
function forEachDemo(obj) {
if (obj instanceof Array) {
obj.forEach(function (item, index) {
console.log(index, item);
})
} else if (obj instanceof Object) {
for (var key in obj) {
console.log(key, obj[key]);
}
}
}