前端常见面试题---3
# JavaScript高级面试题
# 1. 判断以下程序的输出结果:
var age=100;
function test(){
this.age=50;
return function(){
return this.age;
}
}
var m=new test();
alert(m());
var n=test();
alert(n());
2
3
4
5
6
7
8
9
10
11
# 结果:100,50
构造函数一旦返回一个对象,就不再创建新对象
m获得的是function(){ return this.age; }
n=test(),this指向window。先将全局变量age变为50,又返回一个函数function(){ return this.age; }保存在变量n中
调用n时,this指向window。
# 2. 判断以下程序的输出结果:
var name="The Window";
var obj={
name:"My obj",
getName:function(){
return function(){
return this.name;
}
}
};
console.log(obj.getName()());
2
3
4
5
6
7
8
9
10
# 结果:the window
obj.getName() 返回一个函数对象function(){ return this.name; } (function(){ return this.name; }()) 相当于匿名函数自调,this指向window
# 3. 判断以下程序的输出结果:
var length=10;
function fn(){
console.log(this.length);
}
var obj={
length:5,
method:function(fn){
fn();
arguments[0]();
}
};
obj.method(fn,1)
2
3
4
5
6
7
8
9
10
11
12
# 结果:10,2
fn() this指向window,所以输出10
arguments[0]()
属于特殊情况,this->arguments,相当于arguments.0(), 所以,this指向arguments。所以length输出的是obj.method()的参数个数,为2.
# 4. 统计一个字符串中出现次数最多的字符是? 共出现多少次
var str="hello world";
var dict={};
var c="", max=1;
for(var i=0;i<str.length;i++){
var char=str[i];
if(dict[char]===undefined)
dict[char]=1;
else{
dict[char]+=1;
if(dict[char]>max){
max=dict[char];
c=char;
}
}
}
console.log(c,max);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 结果:l ,3
提前创建一个空对象,用于保存每个字母出现的次数。 提前创建变量,准备保存出现次数最多的字符和出现的次数。 然后,遍历字符串中每个字母,每遍历一个字母就判断结果对象中是否包含以当前字母为属性名的属性。如果不包含以当前字母为属性名的属性,说明是首次遇见该字母,就向结果对象中强行添加以该字母为属性名的属性,值暂时为1。如果结果对象中已经包含以当前字母为属性名的属性,说明不是第一次碰见该字母。则取出该字母名属性对应的次数+1。只要当前字母出现的次数>之前变量中记录的最大次数,就用当前字母和出现次数,取而代之。
var str="hello world";//需要统计的字符串
var dic={};//声明一个空对象
//将字符串中所有字符出现的次数做统计
for(var i=0;i<str.length;i++){
if(dic[str[i]]===undefined){
dic[str[i]]=1
}else{
dic[str[i]]+=1
}
}
console.log(dic);//{ h: 1, e: 1, l: 3, o: 2, ' ': 1, w: 1, r: 1, d: 1 }
var max,num=0;
//通过循环找到最多的并替换
for(var key in dic){
if(dic[key]>num){
max=key;
num=dic[key];
}
}
console.log(max,num);//l 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5. 判断以下程序的输出结果:
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i);
},0)
}
console.log(i);
2
3
4
5
6
# 结果:5, 5, 5 ,5, 5, 5
函数定义时,函数内容是不执行的,所以i还是i,不会变成0,1,2,3,4 定时器中的回调函数只能在主程序执行完才能开始执行 当主程序执行完,循环变量i,已经被改为5了。
# 6. 判断以下程序的输出结果:
window.color="red";
let color="green";
let obj={
color:"blue"
};
let sayColor=()=>{
return this.color;
}
console.log(sayColor.apply(obj));
let a=10;
console.log(window.a);
2
3
4
5
6
7
8
9
10
11
# 结果:red, undefined
let相当于匿名函数自调,所以,let声明的变量,不会自动加入到window 箭头函数内外this通用,所以apply也无法替换sayColor函数内的this,所以this指向window,所以输出red
# 7. 判断以下程序的输出结果:
var c=1;
function c(c){
console.log(c);
var c=3;
}
c(2);
2
3
4
5
6
# 结果:报错: TypeError: c不是一个函数(TypeError: c is not a function)
function c(c){} 整体被声明提前,后又被c=1代替。所以,c最后不是一个函数,而是数字1
# 8. 判断以下程序的输出结果:
function change(){
alert(typeof fn)
function fn(){ alert('hello') }
var fn;
}
change();
2
3
4
5
6
# 结果:function
function fn(){…}被整体声明提前了 var fn发现已经有fn变量了,就不再重复创建,所以,var fn没作用。
# 9. 判断以下程序的输出结果:
a=3
a.prop=4;
alert(a+a.prop)
2
3
# 结果:NaN
a.prop=4,等效于new Number(a).prop=4, 但是new Number(a),使用后自动释放,4也不存在了 再次使用a.prop,又等效于新的new Number(a),所以没有prop属性,值为undefined。 数字+undefined, undefined隐式转为数字NaN,导致计算结果为NaN
# 10. 判断以下程序的输出结果:
var o={
a:10,
b:{
a:12,
fn:function(){
var a=13;
console.log(this.a);
}
}
}
o.b.fn();
2
3
4
5
6
7
8
9
10
11
# 结果:12
this指.前的o.b对象,所以a为12
# 11. 判断以下程序的输出结果:
var obj1 = {
name: 'obj1',
fn: function() {
document.write(this.name);
}
};
var obj2 = {name: 'obj2'};
var obj3 = {name: 'obj3'};
obj1.fn();
var newFn = obj1.fn;
newFn();
newFn.call(obj2);
obj3.fn = newFn;
obj3.fn();
2
3
4
5
6
7
8
9
10
11
12
13
14
# 结果:obj1, 空字符串(undefined), obj2 ,obj3
this指.前的obj1
因为newFn调用时,前边没有.,所以this->window,
call是强行替换newFn中的this为obj2
this指.前的obj3
# 12. 一个数组 par 中存放有多个人员的信息,每个人员的信息由年龄 age 和姓名 name 组成,如{age: 2, name: 'xx'}。请写一段 JS 程序,对这个数组按年龄从小到大进行排序。
function sortAge(arr){
return arr.sort(function(a,b){
return a.age-b.age;
})
}
2
3
4
5
数组的sort函数的参数,是一个比较器函数。比较器函数的形参a和b,指当前要排序的数组中的任意两个作比较的元素。如果作比较的a和b两个元素是简单的数字类型,则a直接和b相减,就可比较两数大小。但是,如果a和b都是对象类型的元素。要比较两个对象中某个属性的值得大小,就必须用a.属性-b.属性。如果属性名是灵活的,来自于变量,则必须用a[属性名变量]-b[属性名变量],就可比出两个对象的某个属性值得大小。
最后,数组是引用类型的对象,在函数内修改数组,等效于修改原数组。所以不用返回值。
# 13.有字符串 var str= 'abc345efgabcab',请写出 3 条 JS 语句分别实现如下 3 个功能:
1)去掉字符串中的a、b、c 字符,形成结果:'345efg' 2)将字符串中的数字用中括号括起来,形成结果:'abc[345]efgabcab' 3)将字符串中的每个数字的值分别乘以 2,形成结果:'abc6810efgabcab'
1. str.replace(/([a-c])/g, '');//345efg
2.str.replace(/(\d+)/g, '[$1]');//abc[345]efgabcab
3.str.replace(/(\d)/g, function(num) {return num*2;});//abc6810efgabcab
1)将字符串中的a,b,c三个字符都替换为空字符串 2)找到字符串中多个连续的数字,分为一组,然后将这一组的内容,替换为用[]包裹。$1,可获得关键词中第一个()包裹的内容。 3)找到字符串中每个数字,*2后,再放回原位置。
# 14.判断以下程序的输出结果:
var a=10;
var obj={
a:20,
intr:function(){
var a=30;
console.log(this.a);
}
}
obj.intr();
var intr=obj.intr;
intr();
2
3
4
5
6
7
8
9
10
11
# 结果:20,10
obj.intr(),this指.前的obj,所以输出20 intr(), this指window,所以输出10
# 15. 判断以下程序的输出结果:
function fun(){
for(var i=0,arr=[];i<3;i++){
arr[i]=function(){
console.log(i);
}
}
return arr;
}
var funs=fun();
funs[0]();
funs[1]();
funs[2]();
2
3
4
5
6
7
8
9
10
11
12
# 结果:3,3,3
# 16. 定义函数实现深克隆一个对象:
//准备一个被克隆对象
var lilei={
sname:"Li Lei",
score:null,
friends:["jack","rose"],
address:{
prov:"北京",
city:"北京",
area:"海淀",
street:"海淀南路"
},
sage:11
}
//定义深克隆函数
function clone(obj){
if(obj===null){
return null;
}else if({}.toString.call(obj)==="[object Array]"){
var newArr=[];
newArr=obj.slice();
return newArr;
}
var newObj={};
//遍历原obj的每个属性
for(var key in obj){
//如果原对象中当前属性值是原始类型
if(typeof obj[key]!=="object"){
//在新对象中添加和原对象中同名的属性
newObj[key]=obj[key];
//原始类型复制,就是复制副本
}else{//否则,当前属性不是原始类型的值,再次调用clone函数,继续复制当前属性值
newObj[key]=clone(obj[key])
}
}
return newObj;
}
//进行深克隆
var lilei2=clone(lilei);
console.log(lilei==lilei2);//false
lilei2.address.area="朝阳";
//测试被克隆对象的address.area是否被改变
console.log(lilei.address);//结果还是原样
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
# 17. 介绍 JavaScript 的原型,原型链?有什么特点?
原型:
- JavaScript 的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是该对象的原型
- JavaScript 的函数对象,除了原型 [proto] 之外,还预置了 prototype 属性
- 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]。
原型链:
- 当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找
- 如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
原型特点:
- JavaScript 对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变
# 18. 谈谈Javascript 垃圾回收方法
标记清除(mark and sweep)
- 这是 JavaScript 最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数(reference counting)
- 在低版本 IE 中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加 1,如果该变量的值变成了另外一个,则这个值得引用次数减 1,当这个值的引用次数变为 0 的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的空间
# 19.说说严格模式的限制
严格模式主要有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用 with 语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量 delete prop,会报错,只能删除属性 delete global[prop]
- eval 不会在它的外层作用域引入变量
- eval 和 arguments 不能被重新赋值
- arguments 不会自动反映函数参数的变化
- 不能使用 arguments.callee
- 不能使用 arguments.caller
- 禁止 this 指向全局对象
- 不能使用 fn.caller 和 fn.arguments 获取函数调用的堆栈
- 增加了保留字(比如 protected、static 和 interface)
# 20. 使用正则表达式验证邮箱格式
var reg = /^(\w)+(\.\w+)*@(\w)+((\.\w{2,3}){1,3})$/;
var email = "example@qq.com";
console.log(reg.test(email)); // true
2
3
# 21.使用typeof bar ===“object”来确定bar是否是一个对象时有什么潜在的缺陷?这个陷阱如何避免?
尽管typeof bar ===“object”
是检查bar是否是对象的可靠方法,但JavaScript中令人惊讶的问题null也被认为是一个对象!
因此,对于大多数开发人员来说,下面的代码会将true(而不是false)打印到控制台:
var bar = null;
console.log(typeof bar === "object"); // logs true!
2
只要知道这一点,就可以通过检查bar是否为空来轻松避免该问题:
console.log((bar !== null) && (typeof bar === "object")); // logs false
为了让我们的答案更加的完整,还有两件事值得注意: 首先,如果bar是一个函数,上面的解决方案将返回false。在大多数情况下,这是所期望的行为,但是在您希望函数返回true的情况下,您可以将上述解决方案修改为:
console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));
其次,如果bar是数组,则上述解决方案将返回true(例如,如果var bar = [];)。在大多数情况下,这是所希望的行为,因为数组确实是对象,但是在您想要对数组也是false的情况下,可以将上述解决方案修改为:
console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));
但是,还有一个替代方法对空值,数组和函数返回false,但对于对象则为true:
console.log((bar !== null) && (bar.constructor === Object));
或者,如果您使用jQuery:
console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));
ES5使得数组的情况非常简单,包括它自己的空检查:
console.log(Array.isArray(bar));
# 22. 以下代码的输出是什么?解释你的答案
var a={},
b={key:'b'},
c={key:'c'};
a[b]=123;
a[c]=456;
console.log(a[b]);
2
3
4
5
6
7
8
# 结果:456
原因如下:设置对象属性时,JavaScript会隐式地将参数值串联起来。在这种情况下,由于b和c都是对象,它们都将被转换为“[object Object]”。因此,a [b]和a [c]都等价于[“[object Object]”],并且可以互换使用。因此,设置或引用[c]与设置或引用[b]完全相同。
console.log(a);//{ '[object Object]': 456 }