《前端JavaScript面试技巧》笔记一
思考:
- 拿到一个面试题,你第一时间看到的是什么 -> 考点
- 又如何看待网上搜出来的永远也看不完的题海 -> 不变应万变
- 如何对待接下来遇到的面试题 -> 题目到知识再到题目
知识体系:
JS基础知识
一、变量类型和计算
题目:
- JS中使用typeof能得到哪些类型?
- 何时使用 === 何时使用 == ?
- JS中有哪些内置函数
- JS变量按照存储方式区分为哪些类型,并描述其特点
- 如何理解JSON
知识点:
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
43
44
45
46
47
48
49
|
/*一、变量类型*/ /*值类型vs引用类型*/ //从内存来说,值类型是把每个值分块存放在内存中,而引用类型是好几个变量公用一个内存块,节省内存空间。 //引用类型包括:对象、数组、函数。引用类型的属性是可以无限扩展的,属性多了,就会占用更多的内存空间,所以引用类型是为了公用内存空间。 //值类型 var a = 100; var b = a; a = 200; console.log(b); //100 //引用类型 var a = {age:20}; var b = a; b.age = 21; console.log(a.age); //21 /*typeof运算符详解*/ //typeof只能区分值类型,引用类型也只能区分function,因为function的定位非常高。 typeof undefined //undefined (值类型) typeof 'abc' //string (值类型) typeof 123 //number (值类型) typeof true //boolean (值类型) typeof {} //object (引用类型) typeof [] //object (引用类型) typeof null //object (引用类型) typeof console.log //function (引用类型) /*二、变量计算---强制类型转换*/ //字符串拼接 var a = 100+10; //110 var b = 100+ '10' ; //'10010' //运算符 //==会把两边的值转换为true或false 100 == '100' ; //true 0 == '' ; //true null == undefined; //true //if语句 //if会把括号里面的值转换为true或false var a = true ; if (a){...} var b = 100; if (b){...} var c = '' ; if (c){...} //逻辑运算 console.log(10 && 0); //0 console.log( '' || 'abc' ); //'abc' console.log(!window.abc); //true //判断一个变量会被当作true还是false var a = 100; console.log(!!a); |
解题:
JS中使用typeof能得到哪些类型?
undefined、string、number、boolean、object、function
何时使用 === 何时使用 == ?
1
2
3
4
|
if (obj.a== null ){ //这里相当于 obj.a === null || obj.a ===undefined ,简写形式 //这是 jquery 源码中推荐的写法 } |
双等会进行强制类型转换,三等不会进行强制类型转换。
除了上面的例子用双等,其它的都用三等。
JS中有哪些内置函数
数据封装类对象
Object
Array
Boolean
Number
String
Function
Date
RegExp :正则表达式
Error
JS变量按照存储方式区分为哪些类型,并描述其特点
值类型和引用类型。
从内存来说,值类型是把每个值分块存放在内存中,而引用类型是好几个变量公用一个内存块,节省内存空间。
如何理解JSON
JSON只不过是一个JS对象,也是一种数据格式。
JSON基本的api只有两个:
JSON.stringify({a:10,b:20}); //对象转字符串
JSON.parse('{"a":10,"b":20}'); //字符串转对象
二、原型和原型链
题目:
- 如何准确判断一个变量是数组类型
- 写一个原型链继承的例子
- 描述new一个对象的过程
- zepto(或其他框架)源码中如何使用原型链
知识点:
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
//大写开头的函数基本都是构造函数,这么写提高代码可读性 /*一、构造函数*/ function Foo(name,age){ this .name = name; this .age = age; this . class = 'class-1' ; //return this //默认有这一行,最好不要写 } var f = new Foo( 'zhangsan' ,20); //var f1 = new Foo('lisi',22); //创建多个对象 //new时把参数传进去。new函数执行时里面的this会变成空对象,给this赋值后,再把this给return回来,return回来就把值赋值给了f,这时f就具备f.name = 'zhangsan',f.age = 20,f.class ='' 'class-1' /*二、构造函数-扩展*/ /* var a = {} 其实是 var a = new Object() 的语法糖。 构造函数是Object函数。 var a = [] 其实是 var a = new Array() 的语法糖。 构造函数是Array函数。 function Foo(){...} 其实是 var Foo = new Function(...)。 构造函数是Function。 推荐前面的书写方式。 使用instanceof判断一个函数是否是一个变量的构造函数。比如:判断一个变量是否为“数组”:变量 instanceof Array */ /*三、原型规则和示例*/ //5条原型规则-原型规则是学习原型链的基础 //1、所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了null以外) var obj = {}; obj.a = 100; var arr = []; arr.a = 100; function fn(){}; fn.a = 100; //2、所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象。 __proto__ 隐式原型 console.log(obj.__proto__); console.log(arr.__proto__); console.log(fn.__proto__); //3、所有的函数,都有一个prototype属性,属性值也是一个普通的对象。 prototype显式原型 console.log(fn.prototype); //4、所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的"prototype"属性值。 console.log(obj.__proto__ === Object.prototype); //5、当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 __proto__(即它的构造函数的prototype)中寻找。 //构造函数 function Foo(name,age){ this .name = name; } Foo.prototype.alertName = function (){ alert( this .name); } //创建示例 var f = new Foo( 'zhangsan' ); f.printName = function (){ console.log( this .name); } //测试 f.printName(); //zhangsan f.alertName(); //zhangsan //补充 -- 循环对象自身的属性 var item; for (item in f){ //高级浏览器已经在 for in 中屏蔽了来自原型的属性 //但是这里建议还是加上这个判断,保证程序的健壮性 if (f.hasOwnProperty(item)){ console.log(item); } } /*四、原型链*/ //构造函数 function Foo(name,age){ this .name = name; } Foo.prototype.alertName = function (){ alert( this .name); } //创建示例 var f = new Foo( 'zhangsan' ); f.printName = function (){ console.log( this .name); } //测试 f.printName(); //zhangsan f.alertName(); //zhangsan f.toString(); //要去f.__proto__.__proto__中查找 /*五、instanceof*/ //用于判断引用类型属于哪个构造函数的方法 //构造函数 function Foo(name,age){ this .name = name; } Foo.prototype.alertName = function (){ alert( this .name); } //创建示例 var f = new Foo( 'zhangsan' ); f.printName = function (){ console.log( this .name); } //测试 f.printName(); //zhangsan f.alertName(); //zhangsan f.toString(); //要去f.__proto__.__proto__中查找 //instanceof //f instanceof Foo 的判断逻辑是: //1、f 的 __proto__ 一层一层往上,能否对应到 Foo.prototype //2、再试着判断 f instanceof Object |
解题:
如何准确判断一个变量是数组类型
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(); /*面试时千万不要写这个例子,要写更贴近于实战的原型链例子*/ |
1
|
/*用下面的例子*/ |
//写一个封装DOM查询的例子
function Elem(id){
this.elem = document.getElementById(id);
}
Elem.prototype.html = function(val){
var elem = this.elem;
if(val){
elem.innerHTML = val;
return this; //链式操作
}else {
return elem.innerHTML;
}
}
Elem.prototype.on = function(type,fn){
var elem = this.elem;
elem.addEventListener(type,fn);
return this;
}
var div1 = new Elem('div1');
console.log(div1.html());
div1.html('<h2>新内容</h2>').on('click',function(){
alert(div1.html());
}).html('<h2>新内容新内容新内容</h2>').on('click',function(){
alert('第二次');
}); //链式操作
/*div1.html('<h2>新内容</h2>')
div1.on('click',function(){
alert(div1.html());
})*/
1
|
|
描述new一个对象的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/* 1、创建一个新对象 2、this指向这个新对象 3、执行代码,即对this赋值 4、返回this */ /*构造函数*/ function Foo(name,age){ this .name = name; this .age = age; this . class = 'class-1' ; //return this //默认有这一行 } var f = new Foo( 'zhangsan' ,20); //var f1 = new Foo('lisi',22); //创建多个对象 |
zepto(或其他框架)源码中如何使用原型链
- 阅读源码是高效提高技能的方式。如zepto
- 但不能“埋头苦钻”,有技巧在其中
- 慕课网搜索“zepto设计和源码分析”
三、作用域和闭包
题目:
- 说一下对变量提升的理解
- 说明this几种不同的使用场景
- 创建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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
/*一、执行上下文*/ //范围:一段<script>或者一个函数 //全局:变量定义、函数声明(一段<script>) //函数:变量定义、函数声明、this、arguments(函数) //PS:注意“函数声明”和“函数表达式”的区别 //实际代码中不要下面这种写法,都是先定义,再执行,提高代码可读性。 console.log(a); //undefined var a = 100; fn( 'zhangsan' ); //'zhangsan' 20 function fn(name){ //函数声明 age = 20; console.log(name,age); var age; } var fn = function (){} //函数表达式 /*二、this*/ //this要在执行时才能确认值,定义时无法确认。 //作为构造函数执行 //作为对象属性执行 //作为普通函数执行 //call、apply、bind var a = { name: 'A' , fn: function (){ console.log( this .name); } } a.fn(); //this===a a.fn.call({name: 'B' }); //this==={name:'B'} var fn1 = a.fn; fn1(); //this===window /*作用域*/ /*无块级作用域*/ //没有块级作用域,写在里面和外面是一样的。不建议下面这种写法,程序不易读。 if ( true ){ var name = 'zhangsan' ; } console.log(name); //zhangsan /*函数和全局作用域*/ var a = 100; //全局变量 function fn(){ var a = 200; //局部变量 console.log( 'fn' ,a); } console.log( 'global' ,a); fn(); /*作用域链*/ //函数的父级作用域是函数定义时的作用域,不是函数执行时的作用域。 var a = 100; function fn(){ var b = 200; //当前作用域没有定义的变量,即“自由变量” console.log(a); //100 console.log(b); //200 } fn(); //例子 var a = 100; function F1(){ var b = 200; function F2(){ var c = 300; console.log(a); //100 //自由变量 console.log(b); //200 //自由变量 console.log(c); //300 } F2(); } F1(); /*闭包*/ function F1(){ var a = 100; //返回一个函数(函数作为返回值) return function (){ console.log(a); } } //f1得到一个函数 var f1 = F1(); var a = 200; f1(); //f1执行的是return里的函数,return里的a是个自由变量,要去父级作用域寻找,父级作用域F1里面定义了a,所以打印出来的a的值是100. /*闭包的使用场景 1、函数作为返回值(上一个demo) 2、函数作为函数传递(下面这个例子) */ function F1(){ var a = 100; return function (){ console.log(a); } } var f1 = F1(); function F2(fn){ var a = 200; fn(); } F2(f1); |
解题:
说一下对变量提升的理解
变量定义
函数声明(注意和函数表达式的区别)
执行上下文的概念。
各个函数中,它的变量的声明和定义,以及函数的声明都会提前,放在前面,由此就是变量提升主观上、形象上的理解。
说明this几种不同的使用场景
作为构造函数执行
作为对象属性执行
作为普通函数执行
call、apply、bind
创建10个<a>标签,点击时弹出对应的序号
1
2
3
4
5
6
7
8
9
10
11
12
|
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); } |
如何理解作用域
要领:
1、自由变量
2、作用域链,即自由变量的查找
3、闭包的两个场景
实际开发中闭包的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//闭包实际应用中主要用于封装变量,收敛权限 function isFirstLoad(){ var _list = []; return function (id){ if (_list.indexOf(id)>=0){ return false ; } else { _list.push(id); return true ; } } } //使用 var firstLoad = isFirstLoad(); firstLoad(10); //true firstLoad(10); //false firstLoad(20); //true//在 isFirstLoad 函数外面,根本不可能修改掉 _list 的值 |
四、异步和单线程
题目:
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 一个关于setTimeout的笔试题
- 前端使用异步的场景有哪些
知识点:
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
/*什么是异步(对比同步)*/ //同时执行,不会阻塞程序执行 console.log(100); setTimeout( function (){ console.log(200); },1000); console.log(300); setTimeout( function (){ console.log(400); },1000); //下面这个例子类似同步 console.log(100); alert(200); console.log(300); /*前端使用异步的场景*/ /*何时需要异步: 在可能发生等待的情况 等待过程中不能像alert一样阻塞程序进行 因此,所有的“等待的情况”都需要异步 1、定时任务:setTimeout、setInterval 2、网络请求:ajax请求、动态<img>加载 3、事件绑定 */ //ajax请求代码示例 console.log( 'start' ); $.get( './data1.json' , function (data1){ console.log(data1); }) console.log( 'end' ); //<img>加载示例 console.log( 'start' ); var img = document.createElement( 'img' ); img.onload = function (){ console.log( 'loaded' ); } img.src = '/xxx.png' ; console.log( 'end' ); //事件绑定示例 console.log( 'start' ); document.getElementById( 'btn1' ).addEventListener( 'click' , function (){ alert( 'clicked' ); }) console.log( 'end' ); /*异步和单线程*/ console.log(100); setTimeout( function (){ console.log(200); }); console.log(300); //执行结果:100、300、200 //setTimeout是异步,最后执行。这个例子的setTimeout没有等待时间,所以待其它执行完之后立马执行setTimeout。 //单线程就是一次只能做一件事 /*解析: 1、执行第一行,打印100 2、执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点,不能同时干两件事) 3、执行最后一行,打印300 4、待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行。 5、发现暂存起来的setTimeout中的函数无需等待时间,就立即拿过来执行。 */ |
解题:
同步和异步的区别是什么?分别举一个同步和异步的例子
同步会阻塞代码执行,而异步不会。
alert是同步,setTimeout是异步。
例子:
1
2
3
4
5
6
7
8
9
10
|
//异步 console.log(100); setTimeout( function (){ console.log(200); },1000); console.log(300); //同步 console.log(100); alert(200); console.log(300); |
一个关于setTimeout的笔试题
1
2
3
4
5
6
7
8
9
10
11
|
console.log(1); setTimeout( function (){ console.log(2); },0) console.log(3); setTimeout( function (){ console.log(4); },1000) console.log(5); //1 3 5 2 4 |
前端使用异步的场景有哪些
- 定时任务:setTimeout、setInterval
- 网络请求:ajax请求、动态<img>加载
- 事件绑定
五、其它知识点
题目:
- 获取2017-06-10格式的日期
- 获取随机数,要求是长度一致的字符串格式
- 写一个能遍历对象和数组的通用forEach函数
知识点:
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
/*日期*/ 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(); //获取随机数。(返回的是 >0 和 <1 的小数)。有清除缓存的作用。 /*数组API*/ //forEach //遍历所有元素 var arr = [1,2,3]; arr.forEach( function (item,index){ //遍历数组的所有元素。item:元素的值。index:元素的位置 console.log(index,item); }) //every //判断所有元素是否都符合条件 var arr = [1,2,3]; // var arr = [1,2,3,4,5]; var result = arr.every( function (item,index){ if (item<4){ return true ; } }) console.log(result); //true //some //判断是否有至少一个元素符合条件 var arr = [1,2,3]; var result = arr.some( function (item,index){ if (item<2){ return true ; } }) console.log(result); //true //sort //排序 var arr = [1,4,2,5,3]; var arr2 = arr.sort( function (a,b){ //从小到大排序 return a -b; //从大到小排序 //return b-a; }) console.log(arr2); //map //对元素重新组装,生成新数组 var arr = [1,2,3,4]; var arr2 = arr.map( function (item,index){ return '<b>' + item + '</b>' ; }) console.log(arr2); //filter //过滤符合条件的元素 var arr = [1,2,3,4]; var arr2 = arr.filter( function (item,index){ if (item>=2){ return true ; } }) console.log(arr2); /*对象API*/ var obj = { x:100, y:200, z:300 } var key; for (key in obj){ //key是obj的属性名 //注意这里的 hasOwnProperty ,再讲原型链时候讲过了 if (obj.hasOwnProperty(key)){ //判断key是obj原生的属性,而不是原型里面的属性 console.log(key,obj[key]); } } |
解题:
获取2017-06-10格式的日期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function formatDate(dt){ if (!dt){ dt = new Date(); } var year = dt.getFullYear(); var month = dt.getMonth() + 1; 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(); console.log(formatDate); |
获取随机数,要求是长度一致的字符串格式
1
2
3
4
|
var random = Math.random(); var random = random + '0000000000' ; //后面加上10个零,为了确保长度一致 var random = random.slice(0,10); //截取前10位 console.log(random); |
写一个能遍历对象和数组的通用forEach函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function forEach(obj,fn){ var key; if (obj instanceof Array){ //准确判断是不是数组 obj.forEach( function (item,index){ fn(index,item); }) } else { //不是数组就是对象,对象用for in循环 for (key in obj){ fn(key,obj[key]); } } } var arr = [1,2,3]; //注意,这里参数的顺序换了,为了和对象的遍历格式一致 forEach(arr, function (index,item){ console.log(index,item); }) var obj = {x:100,y:200}; forEach(obj, function (key,value){ console.log(key,value); }) |