1.JavaScript面试
Posted 菲比寻常的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1.JavaScript面试相关的知识,希望对你有一定的参考价值。
1.1. 简要描述 javascript 的数据类型?
参考答案:
JavaScript 的数据类型可以分为原始类型和对象类型。
原始类型包括 string、number 和 boolean 三种。其中,字符串是使用一对单引号 或者一对双引号括起来的任意文本;而数值类型都采用 64 位浮点格式存储,不区分整数和 小数;布尔(逻辑)只能有两个值:true 或 false。
复杂类型指其他对象,如 Array、Date、Object 等。
除此之外,JavaScript 中还有两个特殊的原始值:null(空)和 undefined(未定义), 它们代表了各自特殊类型的唯一成员。
1.2. 读代码,写结果
写出下列表达式的计算结果:
var a = [];
var b = a;
b[0] = 1;
console.log(a[0]); //1
console.log(b[0]); //1
console.log(a===b);//true
解析:将对象赋值给变量,仅仅是赋值的引用,对象本身并没有赋值一次,因此,变量 a 和 b
指向同一个数组
1.3. 简要描述 null 和 undefined 的区别
参考答案:
null:是 JavaScript 的关键字,用于描述“空值”,对其执行 typeof 操作,返回“object”, 即为一个特殊的对象值,可以表示数字、字符串和对象是“无值”的。
undefined:是预定义的全局变量,其值为“未定义”,它是变量的一种取值,表示变
量没有初始化。当查询对象属性、数组元素的值时,如果返回 undefined 则表示属性或者 元素不存在;如果函数没有任何返回值,也返回 undefined。
需要注意的是,虽然 null 和 undfined 是不同的,但是因为都表示“值的空缺”,两 者可以互换。因此,使用“==”认为二者是相等的,需要使用“===”来区分它们。
1.4. 读代码,写结果
写出下列表达式的计算结果:
10 + "objects" "7" * "4"
1 - "x"
1 + {}
参考答案:
上述代码的结果分别为:
10objects //转换为字符串
28 //转换为字符
NaN //无法转换为数值进行计算,因此返回 NaN 1[object Ojbect] //返回对象的 toString()结果,按照字符串相加 2 //bool 类型转换为数值类型
2 //null 转换为数值 0
1.5. 读代码,写结果
写出下列代码的输出结果:
var a = 2;
var obj = { x: 1, y: { z: 2} };
var n = [obj,3,[4,5]]; console.log(a << 2); console.log(obj["y"].z);
console.log(n[0].y["z"]);
console.log(n[2]["1"]); delete n[0]; console.log(n[0].x);
参考答案:
上述代码的输出结果分别为:
8
2
2
5
console.log(n[0].x);此行代码错误,无法输出有效信息,会输出 Error 信息
1.6. 阅读如下代码:
var x = 10; var y = 20;
var z = x < y ? x++ : ++y;
上述代码运行后,变量 x、y 和 z 的值为多少?
参考答案:
上述代码运行后,变量 x 的值为 11;变量 y 的值为 20;变量 z 的值为 10。 这是因为,运行第三行代码时,只执行?后的第一个语句,因此,y 的值不发生变化,
仍为20;并返回 x 的值赋值给变量 z,因此 z 的值为 10;然后将 x 的值增加 1,变为 11。
1.7. 什么是“逻辑短路”? 参考答案:
逻辑短路是对于逻辑运算而言,是指,仅计算逻辑表达式中的一部分便能确定结果,而
不对整个表达式进行计算的现象。
对于“&&”运算符,当第一个操作数为 false 时,将不会判断第二个操作数,因为此 时无论第二个操作数为何,最后的运算结果一定是 false;
对于“||”运算符,当第一个操作数为 true 时,将不会判断第二个操作数,因为此时无 论第二个操作数为何,最后的运算结果一定是 true。
1.8. 阅读如下代码
var empAge = 20; var empName;
if (empAge > 20 && empName.length > 0) { console.log(1);
}
else {
console.log(2);
}
上述代码运行后,将产生有效输出,还是代码错误?
参考答案:
上述代码运行,会输出 2,而不会发生错误。
这是因为, if 条件中的逻辑表达式的第一个条件(empAge > 20)不满足,会返回 false,此时,将发生“逻辑短路”,而不会继续判断下一个条件。因此,即使下一个条件中 的变量 empName 没有赋值,此时如果计算 empName.length 将发生错误;但是,因为 发生了“逻辑短路”,不会计算此表达式,因此不会发生错误。
既然 if 条件中的逻辑表达式返回 false,则运行 else 语句:输出 2。
1.9. 解释一下 JavaScript 中的局部变量与全局变量的区别
参考答案:
全局变量拥有全局作用域,在 JavaScript 代码的任何地方都可以访问;在函数内声明的变量只在函数体内有定义,即为局部变量,其作用域是局部性的。 需要注意的是,在函数体内声明局部变量时,如果不使用 var 关键字,则将声明全局变量。
1.10.读代码,写结果
写出下列代码的输出结果:
var x = "global"; function test() { var x = "local"; return x;
}
console.log(test());
参考答案:
上述代码的输出结果为 local。这是因为,在函数 test() 体内,局部变量的优先级高 于同名的全局变量。因此,如果在函数体内声明的局部变量和全局变量重名,则以局部变量 优先。因此,调用函数 test() 时,返回的是局部变量 x,其值为 local。
1.11.什么是 JavaScript 中的函数作用域
参考答案:
JavaScript 中的函数作用域是指:变量在声明它的函数体以及这个函数体嵌套的任意
函数体内都是有定义的。这意味着,在函数体内声明的所有变量在整个函数体内始终是可见 的,这种特性也被称为“声明提前”,即,函数内声明的所有变量(不涉及到赋值)都被提 前至函数的顶部声明。
比如,查看如下代码
function test() { console.log(x); var x = 10; console.log(x);
}
test();
上述代码运行,将先输出 undefined,再输出 10。
这是因为,虽然变量 x 在第二行声明并赋值,但其有效范围为整个函数体,因此,第一行代码输出时,表示变量 x 已经声明但是没有赋值,因此输出 undefined;第三行代码
运行时,因为变量 x 已经赋值为 10,则输出 10。
1.12.读代码,写结果
写出下列代码的输出结果:
function test() { var sum = 0;
for (var i = 0; i < 10; i++) {
sum += i;
}
console.log(sum); console.log(i);
}
test();
参考答案:
上述代码中,输出 sum 的值为 45;输出 i 的值为 10。
这是因为,在函数 test() 体内,循环计算完毕后,变量 sum 的值为从 0 累加到 10 的和,即 45;变量 i 虽然是在 for 循环中声明,但是在整个函数体内都有效(函数作用域), 因此,循环完毕后,变量 i 的值为 10。
1.13.读代码,写结果
写出下列代码的输出结果:
var x = "global"; function test() {
console.log(x); var x = "local"; console.log(x);
}
test();
参考答案:
上述代码中,先输出 undefined,再输出 local。
函数 test() 体内声明了与全局变量同名的局部变量 x,则将覆盖全局变量,即局部变 量优先。因此,第一次输出变量 x 时,为输出局部变量 x,此时变量 x 只有声明而没有赋 值,因此输出 undefined;第二次输出变量 x 时,局部变量 x 已经赋值,因此输出字符串 local。
1.14.简述 arguments 对象的作用
参考答案:
在函数代码中,使用特殊对象 arguments 可以访问函数的参数。即,开发者在定义
函数时,无需明确的为方法声明参数,也可以在方法体中使用 arguments 来访问参数。 这是因为, arguments 是一种特殊对象,在函数代码中,表示函数的参数数组。
正因为 arguments 表示参数组成的数组,因此,首先可以使用 arguments.length
检测函数的参数个数,其次,可以通过下标(arguments[index])来访问某个参数。这样, 可以用 arguments 对象判断传递给函数的参数个数并获取参数,适用于函数参数无法确 定个数的情况下。
1.15.简要描述 JavaScript 中定义函数的几种方式
参考答案:
JavaScript 中,有三种定义函数的方式:
1、函数语句:即使用 function 关键字显式定义函数。如:
function f(x){
return x+1;
}
2、函数定义表达式:也称为“函数直接量”。形如:
var f = function(x){return x+1;};
3、使用 Function() 构造函数定义,形如:
Var f = new Function(“x”,”return x+1;”);
1.16.读代码,写结果
写出下列代码的输出结果:
var f = function (x) { return x * x; }
console.log(f);
console.log(f(10));
参考答案:
上述代码运行时,先输出 function (x) { return x * x; };再输出 100。 这是因为,变量 f 代表一个函数对象,因此直接输出变量时,将输出函数体对应的字
符文本;f(10)表示调用变量 f 所对应的函数,返回 100 并输出。
1.17.阅读如下代码
function f() { console.log("function");
}
function test() { console.log(f); f();
f = "hello"; console.log(f); f();
}
test();
上述代码运行后,输出结果为?
参考答案:
上述代码运行,会先输出
function f() {
console.log("function");
};
然后输出 function;再输出 hello;然后会输出异常信息:string is not a function。 这是因为,定义函数时,函数名称作为一个全局变量,该变量指向函数对象。因此,执 行函数 test 中的第一行代码时,将输出变量 f 所对应的函数对象,即输出函数体中代码 的字符串形式;然后执行 f()表示调用方法 f,则输出字符串“function”;
执行代码 f=“hello”,意味着将变量 f 的值改为字符串,因此再输出变量 f 时,将输出字符串“Hello”; 而如果试图再执行 f(),会引发错误。这是因为,此时,变量 f 不再是一个函数对象,而是一个普通字符串。
1.18.列举几个 JavaScript 中常用的全局函数,并描述其作用
参考答案
JavaScript 中常用的全局函数及其作用如下:
1. parseInt:解析一个字符串并返回一个整数;
2. parseFloat:解析一个字符串并返回一个浮点数;
3. isNaN:检查某个值是否是数字,返回 true 或者 false;
4. encodeURI :把字符串作为 URI 进行编码;
5. decodeURI :对 encodeURI() 函数编码过的 URI 进行解码;
6. eval:计算某个字符串,以得到结果,或者用于执行其中的 JavaScript 代码。
1.19.阅读如下代码
function printArray(arr) { for (var i in arr) {
if (arr[i] instanceof Array) { printArray(arr[i]);
} else {
document.write(arr[i] + ‘ ‘);
}
}
}
var data = [1, [20, 21], [[301, 302], [310, 311]]];
printArray(data);
上述代码运行后,页面的输出结果为?
参考答案:
上述代码运行,会在页面输出:1 20 21 301 302 310 311。
函数 printArray 使用了递归方式,逐一输出数组中的每个成员,中间以空格隔开。
1.20.编写函数,实现冒泡排序
参考答案:
使用 JavaScript 编写的冒泡排序函数如下所示:
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) {
var temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp;
}
}
}
}
测试函数 bubbleSort,代码如下:
var arr = [12, 4, 9, 21, 43, 3];
bubbleSort(arr); console.log(arr);
上述代码运行时,将输出排序后的结果:[3, 4, 9, 12, 21, 43]。
1.21.编写函数,实现插入排序
参考答案:
插入排序是指,先假定将 n 个元素的数列分为已有序和无序两个部分;然后将无序数
列的第一个元素与有序数列的元素从后往前逐个进行比较,找出插入位置,将该元素插入到 有序数列的合适位置中。过程如下图所示:
使用 JavaScript 编写的插入排序函数如下所示:
function insertionSort(arr) {
//从第二个元素开始
for (var i = 1; i < arr.length; i++) {
// 取出待比较的元素
var temp = arr[i];
// 向前找,找到比当前元素大的位置
var p=i-1;
while(temp<arr[p]&&p>=0){
arr[p+1]=arr[p];
p--;
}
// 插入元素
arr[p + 1] = temp;
}
}
测试函数 insertionSort,代码如下:
var arr = [12, 4, 9, 21, 43, 3];
insertionSort(arr); console.log(arr);
上述代码运行时,将输出排序后的结果:[3, 4, 9, 12, 21, 43]。
1.22.编写函数,实现对身份证号码最后一位的验证
二代身份证号码为 18 位,其最后一位(第 18 位)的计算方法为:
1、 将前面的身份证号码 17 位数分别乘以不同的系数。从第一位到第十七位的系数分 别为:
7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2
2、 将这 17 位数字和系数相乘的结果相加
3、 用加出来和除以 11,看余数是多少?
4、 余数只可能有 0-1-2-3-4-5-6-7-8-9-10 这 11 个数字。每个数字所对应 的最后一位身份证的号码为:1-0-X-9-8-7-6-5-4-3-2。即,如果余数 是 2,就会在身份证的第 18 位数字上出现罗马数字的Ⅹ。如果余数是 10,身份证的最后一位号码就是 2。
例如:某男性的身份证号码是 34052419800101001X。验证其最后一位是否正确时, 首先需要得出前 17 位的乘积和是 189,然后用 189 除以 11 得出的结果是 17+2/11,也就 是说其余数是 2。最后通过对应规则就可以知道余数 2 对应的数字是 x。所以,可以判定此 身份证号码的最后一位是合格的。
参考答案:
编写验证方法如下:
//验证方法
function verifyCode(id){ if(id.length !=18 )
return false;
/*1、从第一位到第十七位的系数分别为:
7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2
将这 17 位数字和系数相乘的结果相加。 */
var arr = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];
var sum = 0;
for(var i=0; i<arr.length; i++){
sum += parseInt(id.charAt(i)) * arr[i];
}
//2、用加出来和除以 11,看余数,
var c = sum%11;
//3、分别对应的最后一位身份证的号码为:1-0-X-9-8-7-6-5-4-3-2
var ch = [‘1‘, ‘0‘, ‘X‘, ‘9‘, ‘8‘, ‘7‘, ‘6‘, ‘5‘, ‘4‘, ‘3‘, ‘2‘];
var code = ch[c];
var last = id.charAt(17); last = last==‘x‘ ? ‘X‘: last;
return last == code;
}
测试该方法:
var id = "34052419800101001X";
console.log(verifyCode(id));
1.23.读代码,写结果
写出下列代码的输出结果:
var arr1 = [10, 20];
arr1.push(30);
arr1.push([40, 50]);
var data = arr1.pop();
console.log(data);
参考答案:
上述代码的输出结果为 [40, 50]。
数组的方法 push() 表示入栈,即在栈顶(数组尾端)添加指定的元素;方法 pop() 表 示出栈,删除并返回栈顶(数组尾端)的元素。
代码中,第一次入栈为数字 30;第二次入栈为数组 [40,50],且该数组排在栈顶。因 此,调用方法 pop() 时,将删除并返回栈顶元素 [40,50],这是一个数组,因此输出结果
为[40, 50]。
1.24.什么是栈?在 JavaScript 中,如何模拟栈操作?
参考答案:
栈(stack)是一种运算受限的线性表,其限制是仅允许在表的一端进行插入和删除运
算。这一端被称为栈顶,相对的把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈 或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又 称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。栈的操作如 下图所示:
在 JavaScript 中,可以使用数组及其相关操作来模拟栈操作。首先,使用数组存储一
列元素,然后使用数组的 push() 方法在数组的尾部添加指定的元素,类似于在栈顶添加元 素,即顶部入;然后使用数组的 pop() 删除并返回数组尾部的元素,类似于顶部出栈,即 后入的元素先出。
1.25.读代码,写结果
写出下列代码的输出结果:
var arr1 = [10, 20, 30, 40];
arr1.push(50); arr1.shift(); console.log(arr1);
参考答案:
上述代码的输出结果为[20, 30, 40, 50]。
数组的方法 push() 表示入栈,即在栈顶(数组尾端)添加指定的元素,因此,数字 50 将作为数组的最后一个元素;方法 shift() 表示删除并返回栈底(数组头部)的元素, 因此,将从数组删除数值 10。此时,输出数组,将输出剩余的 4 个数值,即[20, 30, 40, 50]。
1.26.什么是正则表达式?在 JavaScript 中,如何应用正则表达式?
参考答案:
正则表达式(Regular Expression) 本身就是一个字符串,由一些普通字符和特殊字符
组成的,用以描述一种特定的字符规则的表达式。 正则表达式常用于在一段文本中搜索、匹配或替换特定形式的文本。如:词语出现频率统计、验证字符串是否符合邮箱格式、屏蔽一篇帖子中的限制性词语等。许多程序设计语言都支持利用正则表达式进行字符串操作。
在 JavaScript 中,正则表达式的应用分为两种:
1、结合 String 对象的 replace、search 和 match 方法,实现对字符串的替换、 查 找和匹配;
2、定义正则表达式对象,实现对字符串的复杂匹配操作。
1.27.读代码,写结果
写出下列代码的输出结果:
var regexp = /\bdo\b/ig;
var data = ‘He does told to Do,do.‘;
console.log(data.search(regexp));
参考答案:
上述代码的输出结果为 16。
String 的 search(regexp) 方法,用于返回第一次出现匹配指定正则表达式的下标, 若没有匹配则返回-1。
试题中,正则表达式 \bdo\b 表示匹配完整的单词 do,且不区分大小写。而变量 data
中,第一次出现单词 do(不区分大小写)的位置为 16。
undefined1.28.阅读如下代码
function add(num) { try {
num = Number(num); if (isNaN(num)) {
throw new Error(‘Arguments is NaN‘);
}
console.log(‘try block end‘);
} catch (e) { console.log(‘catch block‘); return;
} finally {
console.log(‘finally block‘);
}
console.log(‘function end‘);
}
add(‘10x‘);
上述代码运行后,输出结果为?
参考答案:
上述代码运行,会先输出 catch block;再输出 finally block。
这是因为,执行代码 num = Number(num); 时,因为传入的参数值为字符串“10x”,
无法转换为 number 类型,则产生错误,运行到 catch 语句块中,输出“catch bloc”; 而 finally 块始终会运行,因此继续输出“finally block”。程序发生异常后,将退出,因此 不再执行其他语句。
1.29 简要描述 JavaScript 中的匿名函数
参考答案:
匿名函数是指在定义时没有指定名字的函数,且定义后往往直接调用。如:
function(num1,num2) console.log( num1 + num2 );
}
这种方式所定义的匿名函数,往往需要直接调用,如:
(function (num1, num2) { console.log(num1 + num2);
})(10,20);
匿名函数常用于定义不需要重复使用的函数,用完即释放。另外,对于直接调用的匿名
函数而言,可以看成是一个临时的命名空间,其区域内定义的所有变量,不会污染到全局命 名空间。
1.30简要描述 JavaScript 中的作用域链
参考答案:
任何一段 JavaScript 代码都对应一个作用域链,作用域链中存放一系列对象,代码中
声明的变量将作为对象的属性存放。
在 JavaScript 的顶层代码中,作用域链由一个全局对象组成;当定义一个函数时,它 保存一个作用域链,作用域链上有两个对象,一个是函数对象,一个是全局对象。
每当一个函数被调用时,会创建一个活动对象(也叫上下文对象),函数中的局部变量 将作为该对象的属性存放。
当需要使用一个变量时,将从作用域链中逐个查找对象的属性。比如:要使用变量 a, 将先查找作用域中的第一个对象是否有属性 a,如果有就使用;如果没有就查找作用域链中 下一个对象的属性,以此类推。如果作用域链上没有任何一个对象含有属性 x,则认为这段 代码的作用域链上不存在 x,将抛出引用错误异常。
当函数调用完成后,如果没有其他引用指向为此次调用所创建的上下文对象,该对象将
被回收。
1.31.阅读如下代码
var a = 100;
function f() {
var a = 200;
function g() {
return a;
}
return g;
}
console.log(f()());
上述代码运行后,输出结果为?
参考答案:
上述代码运行,会输出 200。
上述代码意味着,先执行 f(),得到该函数内嵌套的一个函数对象 g,然后调用这个嵌 套的函数。
定义函数 f 时,它保存一个作用域链,作用域链上有两个对象,一个是函数对象,一 个是全局对象,此时,全局变量 a 是全局对象的属性,和函数对象 f 无关。
执行函数 f 时,会创建一个活动对象,其中保存变量 a 作为函数 f 的属性而存在。 而函数 g 嵌套定义在函数 f 里,对其访问变量 a 时,它没有变量 a,则继续往下查找函数 f。找到函数 f 的属性 a 并返回。因此,输出结果为 200。
这种函数将变量包裹起来,外部代码可以通过内嵌函数 g 来访问函数 f 内的局部变量
的方式,也称为闭包。
1.32.简要描述你对闭包的理解
参考答案:
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域
内,这种特性称为闭包。这意味着函数变量可以隐藏于作用域链之内,看起来好像是函数将 变量包裹了起来。这种方式常用于共享函数内的私有变量。
闭包有如下应用特征:
1、局部变量:在函数中定义有共享意义(如:缓存、计数器等)的局部变量(注:定 义成全局变量会对外造成污染);
2、内嵌函数:在函数中声明有内嵌函数,内嵌函数对函数中的局部变量进行访问;
3、外部使用:函数向外返回此内嵌函数,外部可以通过此内嵌函数持有并访问声明在 函数中的局部变量,而此变量在外部是通过其他途径无法访问的。
1.33.阅读如下代码
var n = 10;
function counter() {
var n = 0;
var g = function () {
return ++n;
};
return g;
}
var c1 = counter();
console.log(c1());
console.log(c1());
console.log(c1());
上述代码运行后的输出为?
参考答案:
上述代码运行后,分别输出数字 1、2、3。 首先从应用上分析,这是一个闭包的典型应用:模拟计数器。调用一次函数 counter,则得到一个计数器,即代码中的变量 c1,用于统计被调用的次数;每运行一次计数器 c1, 则访问次数加 1。因此分别输出数字 1、2、3。
其次从代码原理来分析:函数 counter 返回其内嵌函数 g,该函数中包裹了函数
counter 的局部变量 n,其初始值为 0;每次运行函数 g,均访问相应的局部变量 n,从 而实现累加计数的效果。
1.34. 阅读如下代码
for (var i = 0; i < 3; i++) {
setTimeout(function () { alert(i); }, 3000);
}
上述代码希望实现:3s 后弹出三次警告对话框,对话框中显示的数字分别为 0、1、2。
问,上述代码能否实现所需要的效果?如果不能,应该如何实现?
参考答案:
上述代码不能实现所需要的效果。会在 3s 后弹出三次警告对话框,对话框中的数字均 为 3。这是因为,循环结束后,变量 i 的值为 3;当 3s 后运行循环中内嵌的函数,执行语句alert(i) 时,访问的是循环中的变量 i。因此,三次弹出均为数值 3。 如果希望实现分别弹出数字 0、1、2,需要将代码修改如下:
for (var i = 0; i < 3; i++) { (function (n){
setTimeout(function () { alert(n); }, 3000);
})(i);
}
修改后的代码中,使用匿名函数封装一次性定时器的代码,并在调用匿名函数时,将变
量 i 的值作为参数传入。每次循环中,调用一次匿名函数,则启动一个定时器对象,将相 应的数字传入。3s 后执行函数时,不再访问循环中的局部变量 i,则会依次弹出 0、1、2。
1.35.简述 JavaScript 中创建自定义对象的方式
参考答案:
自定义对象(user-defined object)指由用户创建的对象,兼容性问题需要由编写者注意。创建自定义对象的方式有:
1、对象直接量
2、new Object()
3、function 对象模板
4、Object.create()
1.36. 查看如下 JavaScript 代码:
var p = new User("mary");
alert(p.name);//弹出 mary
p.introduce();//弹出 i am mary
为使上段代码正确运行,请定义对象 User。
参考答案:
需要使用 function 模板创建对象 User,代码如下:
function User(name) {
this.name = name;
this.introduce = function () {
alert("i am " + this.name);
};
}
1.37. JavaScript 中,this 关键字的作用是什么?
参考答案:
笼统的说,关键字 this 指向当前对象。比如,顶级代码中的 this 指向全局对象;在 指定元素事件的时候,this 指定当前发生事件的元素对象。
对于嵌套函数,如果嵌套函数作为方法被调用,其 this 指向调用它的对象;如果作为 函数调用,this 是全局对象或者为 undefined(严格模式下)。
1.38. 查看如下 JavaScript 代码:
var o = {
m: function () { console.log(this); f();
function f() { console.log(this);
}
}
};
o.m();
请描述上述代码的运行结果。
参考答案:
上述代码在浏览器中运行后,第一行的输出结果类似于:Object {m: function},表示 this 关键字指向调用它的当前对象;第二行的输出结果为 Window,表示 this 关键字指向 全局变量。
这是因为,调用对象 o 的方法 m,在该方法中直接使用 this 关键字,则指向调用它的
对象;然后运行嵌套函数 f 时,this 指向全局对象,在浏览器中运行时,全局对象为Window 对象。需要注意的是,如果使用严格模式,则第二行会输出 undefined。
1.39. 查看如下 JavaScript 代码:
var a = 10; var foo = {
a: 20,
bar: function () { var a = 30; return this.a }
}
console.log(foo.bar()); console.log((foo.bar = foo.bar)());
上述代码运行后,输出结果是?
参考答案:
上述代码运行后,先输出 20。这是因为调用对象 foo 的 bar() 方法时,this 关键字指 向调用它的对象,即 foo,因此,输出该对象中所定义的属性值。
第二行将输出 10。这是属于函数调用,此时,非严格模式下 this 指代全局对象 window, 因此 a 的值为 10。
1.40. 简述你对 JavaScript 中原型的理解
参考答案:
在 JavaScript 中,函数本身就是一个包含了方法和属性的对象。每个函数中都有一个
prototype 属性,该属性所存储的就是原型对象。 原型对象用来保存共享属性和方法,可以通过原型来实现为对象扩展属性,实现继承。
1.41.查看如下 JavaScript 代码:
function Emp(ename, salary) {
this.ename = ename;
this.salary = salary; this.toString = function(){
return this.ename + ":" + this.salary;
};
}
var emp1 = new Emp("mary",3500); var emp2 = new Emp("john", 5500);
Emp.prototype.hireDate = "2015/05/01"; delete emp1.ename;
delete Emp.prototype.hireDate; console.log(emp1.toString() + ":" + emp1.hireDate); console.log(emp2.toString() + ":" + emp2.hireDate);
上述代码运行后,输出结果是?
参考答案:
第一行输出为:undefined:3500:undefined 第二行输出为:john:5500:undefined
JavaScript 中,每个函数都有一个 prototype 属性,该属性引用着该函数的原型对象, 原型对象中包含着当前函数所有实例共享的方法和属性。
使用函数的 prototype 属性可以向函数的原型对象添加或删除属性和方法,并且这些 属性和方法是这一类对象所共享的。
可以使用 delete 关键字删除对象的属性,包括自有属性和原型属性。
1.42.简要描述 JavaScript 中的自有属性和原型属性
参考答案:
自有属性是指,通过对象的引用添加的属性,此时,其它对象可能无此属性。对于自有
属性,是各个对象所特有的、彼此独立的属性。比如:
emp1.job = ‘Coder‘;
原型属性是指从原型对象中继承来的属性,一旦原型对象中属性值改变,所有继承自该 原型的对象属性均改变。比如:
Emp.prototype.dept = ‘研发部‘;
当需要检测对象的自有属性时,可以使用 hasOwnProperty()方法。另,还可以使用 in
操作检测对象及其原型链中是否具备指定属性。 需要注意的是,在检测对象属性时,先检测自有属性,再检测原型属性。
1.43. 查看如下 JavaScript 代码:
function Hero() { this.name = "unknown";
}
Hero.prototype.age = 20; var hero = new Hero();
console.log(‘name‘ in hero); console.log(hero.hasOwnProperty(‘name‘)); console.log(‘age‘ in hero); console.log(hero.hasOwnProperty(‘age‘)); hero.age = 30; console.log(hero.hasOwnProperty(‘age‘));
上述代码运行后,输出结果是?
参考答案:
输出结果分别为 true、true、true 、false 和 true。
因为在构造函数中定义了属性 name,因此变量 hero 拥有自有属性 name。因此在 对 hero 用 in 操作符检测 name 属性时,先检测 name 属性为自有属性还是原型属性, 则输出 true;使用 hasOwnProperty()进行判断也是输出 true。
使用函数的 prototype 向函数的原型添加属性时,添加的属性 age 为原型属性。因 此,下面代码:
console.log(‘age‘ in hero); console.log(hero.hasOwnProperty(‘age‘));
使用 in 操作符判断时,先判断是否为自有属性,如果没有,则在原型链中查找属性
age,因此第一行输出 true;但是 age 并非自有属性,所以第二行输出 false。 此时,再添加代码:
hero.age = 30;
这是为 hero 定义了同名的自有属性 age,其值为 30。因此,此时再检测 hero 对象是 否有自有属性 age 时,将输出 true。
1.44.简要描述你对原型链的理解
参考答案:
每个函数中都有 prototype 属性,该函数被 new 操作符用于创建对象。将一个函数的
prototype 属性指向某个对象,由此形成了一条链,称之为原型链。
可以使用 isPrototypeOf()方法判定一个 prototype 对象是否存在于另一个对象的原型 链中。如果是,返回 true,否则返回 false。
1.45. 简要描述 JavaScript 中的继承
参考答案:
在 JavaScript 中,继承都是源于原型,有多种实现方式。比如:
1. 修改构造函数的原型,为该构造函数创建的对象指定统一的父级对象。代码如下:
B.prototype = new A();
2. 只继承于原型(尽可能地将可重用的属性和方法添加到原型中),代码如下:
B.prototype = A.prototype;
3. 单独修改一个对象的原型,而不影响其他对象的原型。代码如下:
var b1 = new B(); Object.setPrototypeOf(b1, new A());
4. 修改构造函数,这将影响使用该构造函数创建的所有对象。代码如下:
function B() {
Object.setPrototypeOf(this, new A());
}
1.46. 查看如下 JavaScript 代码:
function Hero() { this.name = "unknown";
}
Hero.prototype.name = "Caesar";
var hero = new Hero(); console.log(hero.name);
delete hero.name; console.log(hero.name);
delete Hero.prototype.name; console.log(hero.name);
上述代码运行后,输出结果是?
上述代码运行后,首先输出 unknown;然后输出 Caesar;最后输出 undefined。 查找对象属性时,先查找自有属性,因此,第一次输出的是 hero 的自有属性 name 的值,该属性的值从构造函数中继承而来,即为“unknown“;删除 hero 的自有属性后,再试图输出 name 属性时,则查找其原型链中的属性 name 的值并输出,即为“Caesar”; 继续删除 hero 的原型属性后,则输出“undefined”。
1.47. 查看如下 JavaScript 代码:
function A() { this.name = "a";
this.toString = function () { return this.name };
}
function B() { this.name = "b";
}
var b1 = new B(); console.log(b1.toString());
B.prototype = new A(); var b2 = new B();
console.log(b2.toString());
上述代码运行后,输出结果是?
参考答案:
上述代码运行后,首先输出[object Object];然后输出 b。
这是因为,虽然修改了 B 的原型,但是,只影响修改之后的对象。因此,对象 b1 依 然使用默认的从 Object 继承而来的 toString() 方法,因此输出对象的类型和名称。
对象 b2 是在修改了 B 的原型之后创建的,因此将使用 A 中所定义的 toString() 方 法,将输出字符“b”。
1.48. 简要描述 Call 和 apply 的区别
参考答案:
call()和 apply()都用于间接调用函数。
call 方法用于调用一个对象的一个方法,并以另一个对象替换当前对象。即,任何函 数可以作为任何对象的方法来调用,哪怕这个函数并非那个对象的方法。语法如:
call([thisObj[,arg1[, arg2[, [,.argN]]]]])
其中,第一个参数 thisObj 要调用函数的上下文,即将被用作当前对象的对象。其他参
数为可选参数,表示将被传递方法参数序列。
apply()和 call()在作用上是相同的,但两者在参数上有区别的。它俩的第一个参数相同, 不同的是第二个参数。对于 apply(),第二个参数是一个参数数组,也就是将多个参数组合 成为一个数组传入。如:
func.call(func1,var1,var2,var3) func.apply(func1,[var1,var2,var3])
1.49. 查看如下 JavaScript 代码:
function A() {
this.name = "a";
this.introduce = function () { console.log("My name is " + this.name) };
}
function B() {
this.name = "b";
}
var a1 = new A();
var b1 = new B();
a1.introduce.call(b1);
上述代码运行后,输出结果是?
参考答案:
上述代码运行后,先弹出显示 undefined,再弹出显示 2。
第一行代码运行后,变量 a 和 b 都指向对象{n:1},第二行代码运行,先为对象 {n:1} 添加新属性 x,其值为 {n:2}。此时,对象{n:1}形如 {n:1,x:{n:2}}。然后创建新对象 {n:2}, 并将变量 a 指向这个新对象,而变量 b 依然指向原有对象。
因此,a.x 将输出 undefined,因为此时新对象中只有属性 n 没有属性 x;b.x.n 将表 示向原有对象的中的新属性,因此输出 2。
以上是关于1.JavaScript面试的主要内容,如果未能解决你的问题,请参考以下文章