简答题1. 下面是两个数组,求出这两个数组的交集。
var arr1=[1, 2, 3, 4, 5];
var arr2=[4, 1, 5, 8, 9, 6, 7];
数组的交集是指都包含的元素,可以通过Array对象的两个方法filter()和indexOf()找到两个数组中的交集。filter()方法接收2个参数,第一个参数是一个回调函数,第二个参数是一个可选值,表示执行回调函数时使用的this对象(即调用上下文)。它能过滤掉回调函数结果为假值的元素,然后把剩余的元素组成一个新数组。具体的实现思路是用filter()方法遍历第一个数组,然后用第二个数组是否包含当前元素作为过滤条件,如下所示。
function intersection (arr1, arr2) {
return arr1.filter(function(value, index) {
return arr2.indexOf(value)>=0;
});
}
[考点] 数组
2. 数字1~1000放在含有1001个元素的数组中,其中只有唯一的一个元素值重复,其他数字均只出现一次。设计一个算法,将重复元素找出来,要求每个数组元素只能访问一次。
根据异或运算的性质可知,当相同元素异或时,其运算结果为0;当相异元素异或时,其运算结果为非0;任何数与数字0进行异或运算,其运算结果为该数。本题中,正好可以使用到此方法,即将数组里的元素逐一进行异或运算,得到的值再与数字1,2,3,…,N进行异或运算,得到的最终结果即为所求的重复元素。
以数组[1,3,4,2,5,3]为例。(1^3^4^2^5^3)^(1^2^3^4^5)=(1^l)^(2^2)^(3^3^3)^(4^4)^(5^5)=0^0^3^0^0=3。示例代码如下所示。
function findDup(array) {
var len=array.length,
result=0;
if(!array||len<1)
return-1;
for (var i=0; i<len; i++)
result^=array[i];
for(i=1; i<len; i++)
result^=i;
return result;
}
[考点] 数组
3. 给定数组[a1,a2,a3,...,an],要求找出数组中的最大值和最小值。假设数组中的值各不相同。
可以利用分治法找出数组中的最大值和最小值。分治法就是将一个规模为n的、难以直接解决的大问题,分割为k个规模较小的子问题,采取各个击破、分而治之的策略得到各个子问题的解,然后将各个子问题的解进行合并,从而得到原问题解的一种方法。
本题中,当采用分治法求解时,就是将数组两两一对分组,如果数组元素个数为奇数,就把最后一个元素单独分为一组,再分别对每一组中相邻的两个元数进行比较,把二者中值小的数放在数组的左边,值大的数放在数组右边,只需要比较n/2次就可以将数组分组完成。然后可以得出结论:最小值一定在每一组的左边部分,最大值一定在每一组的右边部分,接着只需要在每一组的左边部分找最小值,右边部分找最大值,查找分别需要比较n/2-1次和n/2-1次;因此,总共比较的次数大约为n/2×3-2次。实现代码如下所示。
function getMaxMin(arr){
var len=arr.length;
if(!arr||len<1){
return;
}
var max,min,tmp;
//两两分组,把较小的数放到左半部分,较大的放到右半部分
for (vari=0;i<len;i++){
if (arr[i+1]!==undefined&& arr[i]>arr[i+1]){
tmp=arr[i];
arr[i]=arr[i+1];
arr[i+1]=tmp;
}
}
//在各个分组的左半部分找最小值
min=arr[0];
for(i=2;i<len; i+=2){
if(arr[i]<min){
min=arr[i];
}
}
//在各个分组的右半部分找最大值
max=arr[l];
for(i=3;i<len; i+=2){
if(arr[i]>max){
max=arr[i];
}
}
//如果数组中元素个数是奇数,最后一个元素被分为一组,需要特殊处理
if(len%2=1){
if(max<arr[len-1])
max=arr[len-1];
if(min>arr[len-1])
min=arr[len-1];
}
console.log("最大值:",max);
console.log("最小值:",min);
}
[考点] 数组
4. 把一个有序数组最开始的若干个元素搬到数组的末尾,称之为数组的旋转。输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。例如数组[3,4,5,1,2]为数组[1,2,3,4,5]的一个旋转,该数组的最小值为1。
通过数组的特性可以发现,数组元素首先是递增的,然后突然下降到最小值,再递增。虽然如此,但是还有下面三种特殊情况需要注意:
(1)数组本身是没有发生过旋转的,还是一个有序的数组,例如序列[1,2,3,4,5,6]。
(2)数组中元素值全部相等,例如序列[1,1,1,1,1,1]。
(3)数组中元素值大部分都相等,例如序列[1,0,1,1,1,1]。
通过旋转数组的定义可知,经过旋转之后的数组实际上可以划分为两个有序的子数组,前面子数组的元素值都大于或者等于后面子数组的元素值。可以根据数组元素的这个特点,采用二分查找的思想不断缩小查找范围,最终找出问题的解决方案,具体实现思路如下。
按照二分查找的思想,给定数组arr,首先定义两个变量low和high,分别表示数组的第一个元素和最后一个元素的下标。按照题目中对旋转规则的定义,第一个元素应该是大于或者等于最后一个元素的(当旋转个数为0,即没有旋转的时候,要单独处理,直接返回数组第一个元素)。接着遍历数组中间的元素arr[mid],其中mid=(high+low)/2。
(1)如果arr[mid]<arr[mid-1],那么arr[mid]一定是最小值。
(2)如果arr[mid+1]<arr[mid],那么arr[mid+1]一定是最小值。
(3)如果arr[high]>arr[mid],那么最小值一定在数组左半部分。
(4)如果arr[mid]>arr[low],那么最小值一定在数组右半部分。
(5)如果arr[low]==arr[mid]且arr[high]=arr[mid],那么此时无法区分最小值是在数组的左半部分还是右半部分(例如[2,2,2,2,1,2]或[2,1,2,2,2,2,2])。在这种情况下,只能分别在数组的左右两部分找最小值minL与minR,最后求出minL与minR的最小值。
示例代码如下所示。
function minNum(x,y){
return(x<y)?x:y;
}
function findMin(arr,low, high){
//如果旋转个数为0(即没有旋转),则单独处理,直接返回数组头元素
if(high<low)
return arr[0];
//只剩下—个元素一定是最小值
if(high==low)
return arr[low];
var mid=low+((high-low)>>1); //采用这种写法防止溢出
if (arr[mid]<arr[mid-1]) //判断arr[mid]是否为最小值
return arr[midl;
if (arr[mid+1]<arr[mid]) //判断arr[mid+1]是否为最小值
return arr[mid+1];
if(arr[high]>arr[mid]) //最小值一定在数组左半部分
return findMin(arr, Iow, mid-1);
if (arr[mid]>arr[low]) //最小值一定在数组右半部分
return findMin(arr, mid+1,high);
//这种情况下无法确定最小值所在的位置,需要在左右两部分分别进行查找
return minNum(findMin(arr, low, mid-1),findMin(arr, mid+1, high));
}
[考点] 数组
5. 给定一个由n-1个整数组成的未排序的数组,其元素是1~n中的不同整数。请写出一个寻找数组中缺失整数的线性时间算法。
首先分析一下数学性质。假设缺失的数字是X,那么这n-1个数一定是1~n之间除了X以外的所有数。试想一下,1~n一共n个数的和是可以求出来的,数组中的元素和也是可以求出来的,二者相减,其值是不是就是缺失的数字X呢?
为了更好地说明上述方法,举一个简单的例子加以说明。假设数组为[2,1,4,5],一共4个元素,n的值为5,要想找出这个缺失的数字,可以首先对1~5五个数字求和,求和结果为15(1+2+3+4+5=15),而数组元素的和为array[0]+array[1]+array[2]+array[3]=2+1+4+5=12,所以,缺失的数字为15-12=3。
通过上面的例子可以很容易形成以下具体思路:定义两个数suma与sumb,其中suma表示的是这n-1个数的和,sumb表示的是这n个数的和,很显然,缺失数字的值即为sumb-suma的值。示例代码如下所示。
function getNum(arr){
var len=arr.length;
if(!arr||len<=0){
return-1;
}
var suma=0,
sumb=0;
for (var i=0; i<len; i++){
suma+=arr[i]; //数组元素相加
sumb+=i+1; //1~n-1相加
}
//与第n个数相加
sumb+=len+1;
return sumb-suma;
}
[考点] 数组
6. 给定一个数组,数组中含有重复元素,给定两个数字num1和num2,求这两个数字在数组中出现位置的最小距离。
可以采用动态规划的方法把每次遍历的结果都记录下来从而减少遍历次数。具体实现思路如下。当遍历数组时,会遇到以下两种情况:
(1)当遇到num1时,记录下num1对应的数组下标lastPos1,通过求lastPos1与上次遍历到的num2下标lastPos2的差可以求出最近一次遍历到的num1与num2的距离。
(2)当遇到num2时,同样记录下它所对应的数组下标lastPos2,然后通过求lastPos2与上次遍历到的num1下标lastPos1,求出最近一次遍历到的num1与num2的距离。
假设给定数组为:[4,5,6,4,7,4,6,4,7,8,5,6,4,3,10,8],num1=4,num2=8。根据以上方法,执行过程如下:
(1)在遍历的时候首先会遍历到4,下标为lastPos1=0,由于此时还没有遍历到num2,因此,没必要计算num1与num2的最小距离。
(2)然后往下遍历,又遍历到num1=4,更新lastPos1=3。
(3)继续往下遍历,又遍历到num1=4,更新lastPos1=7。
(4)接着往下遍历,又遍历到num2=8,更新lastPos2=9;此时,由于前面已经遍历到num1,就可以求出当前num1与num2的最小距离为|lastPos2-lastPos1|=2。
(5)再往下遍历,又遍历到num2=8,更新lastPos2=15;此时,由于前面已经遍历到num1,就可以求出当前num1与num2的最小距离为|lastPos2-lastPos1|=8;由于8>2,所以,num1与num2的最小距离为2。
实现代码如下所示。
function minNum(x,y){
return(x<y)?x:y;
}
function minDistance(arr, num1, num2){
var len=arr.length;
if(!arr||len<=0){
return;
}
var lastPos1=-1, //上次遍历到num1的位置
lastPos2=-1, //上次遍历到num2的位置
minDis=Math.max.apply(null, arr); //num1与num2的最小距离
for(var i=0;i<len;++i){
if(arr[i]=num1){
lastPos1=i;
if(lastPos2>=0)
minDis=minNum(minDis, lastPos1-lastPos2);
}
if(arr[i]==num2){
lastPos2=i;
if(lastPos1>=0)
minDis=minNum(minDis,lastPos2-lastPos1);
}
}
return minDis;
}
[考点] 数组
7. 有一个有n个元素的数组,这n个元素既可以是正数也可以是负数,数组中连续的一个或多个元素可以组成一个连续的子数组,一个数组可能有多个这种连续的子数组,求子数组元素和的最大值。例如:对于数组[1,-2,4,8,-4,7,-1,-5]而言,其具有最大元素和的子数组为[4,8,-4,7],和为15。
由于Sum[i,j]=Sum[i,j-1]+arr[j],所以在计算Sum[i,j]的时候可以使用前面已计算出的Sum[i,j-1]而不需要重新计算。采用这种方法可以省去计算Sum[i,j-1]的时间,因此,可以提高程序的效率。实现代码如下所示。
function maxSubArray(arr) {
var maxSum=Math.min.apply(null, arr),
len=arr.length,
sum;
for (var i=0; i<len; i++) {
sum=0;
for (var j=i; j<len; j++) {
sum+=arr[j];
if(sum>maxSum) {
maxSum=sum;
}
}
}
return maxSum;
}
[考点] 数组
8. 有一个升序排列的数组,数组中可能有正数、负数或0,求数组中绝对值最小的数。例如数组[-10,-5,-2,7,15,50],该数组中绝对值最小的数是-2。
在求绝对值最小的数时可以分为如下三种情况:
(1)如果数组第一个元素为非负数,那么绝对值最小的数肯定为数组第一个元素。
(2)如果数组最后一个元素的值为负数,那么绝对值最小的数肯定是数组最后一个元素。
(3)如果数组中既有正数又有负数,首先找到正数与负数的分界点,如果分界点恰好为0,那么0就是绝对值最小的数。否则通过比较分界点左右的正数与负数的绝对值来确定最小的数。
那么如何来查找正数与负数的分界点呢?最简单的方法仍然是顺序遍历数组,找出第一个非负数(前提是数组中既有正数又有负数),接着通过比较分界点左右两个数的值来找出绝对值最小的数。这种方法在最坏的情况下时间复杂度为O(N)。下面主要介绍采用二分法来查找正数与负数分界点的方法。主要思路如下。取数组中间位置的值a[mid],并将它与0值比较,比较结果分为以下3种情况:
(1)如果a[mid]==0,那么这个数就是绝对值最小的数。
(2)如果a[mid]>0,a[mid-1]<0,那么就找到了分界点,通过比较a[mid]与a[mid-1]的绝对值就可以找到数组中绝对值最小的数;如果a[mid-1]=0,那么a[mid-1]就是要找的数;否则接着在数组的左半部分查找。
(3)如果a[mid]<0,a[mid+1]>0,那么通过比较a[mid]与a[mid+1]的绝对值即可;如果a[mid+1]==0,那么a[mid+1]就是要查找的数;否则接着在数组的右半部分继续查找。
实现代码如下所示。
function findMin(arr) {
var len=arr.length;
if(!arr||len<=0) {
return;
}
if(arr[0]>=0) //数组中没有负数
return arr[0];
if(arr[len-1]<=0) //数组中没有正数
return arr[len-1];
var mid=0,
begin=0,
end=len-1,
absMin=0;
while (1){ //数组中既有正数又有负数
mid=begin+Math.floor((end-begin)/2);
if(arr[mid]==0){ //如果中间值等于0,那么就是绝对值最小的数
return0;
}
if(arr[mid]>0){ //如果中间值大于0,并且正负数的分界点在左侧
if (arr[mid-1]==0)
return0;
if (arr[mid-1]>0)
end=mid-1; //继续在数组的左半部分查找
else
break; //找到正负数的分界点
} else{ //如果中间值小于0,那么在数组右半部分查找
if (arr[mid+1]==0)
return 0;
if(arr[mid+1]<0)
begin=mid+1; //在数组右半部分继续查找
else
break; //找到正负数的分界点
}
}
//求出正负数分界点中绝对值最小的值
absMin=Math.abs(arr[mid])<Math.abs(arr[mid+1])?
arr[mid]:
arr[mid+1];
return absMin;
}
[考点] 数组
9. 把一个含有N个元素的数组循环右移K(K是正数)位,要求时间复杂度为O(N),且只允许使用两个附加变量。
把数组看成由两段组成,记为XY。左旋转相当于要把数组XY变成YX。先在数组上定义一种翻转的操作,就是翻转数组中数字的先后顺序。X翻转后记为XT,显然有(XT)T=X。
先对X和Y两段分别进行翻转操作,这样就能得到XTYT。接着再对XTYT进行翻转操作,得到(XTYT)T=(YT)T(XT)T=YX,正好是期待的结果。
回到原来的题目。根据上述分析,要做的仅仅是把数组分成两段,再定义一个翻转子数组的函数,按照前面的步骤翻转3次就行了。时间复杂度和空间复杂度都合乎要求。
对于数组A=[1,2,3,4,5,6],如何实现对其循环右移2位的功能呢?首先将数组A分成两个部分:A[0~n-k-1]和A[n-k~n-1],再将这两个部分分别翻转,然后放在一起再翻转(反序)。具体是这样的:
(1)翻转1234,123456--->432156。
(2)翻转56,432156--->432165。
(3)翻转432165,432165--->561234。
示例代码如下所示。
function reverse(arr, start, end) {
var temp;
while (start<end) {
temp=arr[start];
arr[start]=arr[end];
arr[end]=temp;
start++;
end--;
}
}
function rightShift(arr, k) {
var len=arr.length;
if(!arr||len<1) {
return;
}
k%=len;
reverse(arr, 0, len-k-1);
reverse(arr, len-k, len-1);
reverse(arr, 0, len-1);
}
[考点] 数组
10. 坐标轴上从左到右的点依次为a[0],a[1],a[2],…,a[n-1],设一根木棒的长度为L,求L最多能覆盖坐标轴的几个点?
本题求满足a[j]-a[i]≤L和a[j+1]-a[i]>L这两个条件的j与i之间所覆盖的点个数的最大值,即(j-i+1)最大值。这样题目就简单多了,方法也很简单:直接从左至0右扫描,使用两个索引i和j,i从位置0开始,j从位置1开始,如果a[j]-a[i]≤L,则执行j++前进,并记录中间经过的点个数;如果a[j]-a[i]>L,则执行j--回退,覆盖的点个数减1,回到刚好满足条件的时候,将满足条件的最大值与前面找出的最大值比较,记录下当前的最大值;然后执行i++、j++,直到求出最大的点个数。有两点需要注意,如下所列:
(1)这里可能不存在i和j使得a[j]-a[i]刚好等于L的情况发生,所以,判断条件不能为a[j]-a[i]==L。
(2)可能存在覆盖点不同但覆盖长度相同的情况发生,此时只选取第一次覆盖的点。
实现代码如下所示。
function maxCover(a, L) {
var n=a.length,
count=2,
maxCount=1, //覆盖的最大点数
start, //覆盖坐标的起始位置
i=0,
j=1;
while(i<n&&j<n){
while((j<n)&&(a[j]-a[i]<=L)){
j++;
count++;
}
j--;
count--;
if(count>maxCount){
start=i;
maxCount=count;
}
i++;
j++;
}
var cover=a.slice(start, start+maxCount);
console.log("覆盖的坐标点: ",cover.join(""))
return maxCount;
}
[考点] 数组
11. 给定一个数组a[N],希望构造一个新数组b[N],其中b[i]=a[0]×a[1]×...×a[N-1]/a[i]。在构造数组的过程中,有如下几点要求:
(a)不允许使用除法。
(b)要求O(l)空间复杂度和O(N)时间复杂度。
(c)除遍历计数器与a[N]和b[N]外,不可以使用新的变量(包括栈临时变量、堆空间和全局变量等)。
(d)请用程序实现并简单描述。
如果没有时间复杂度与空间复杂度的要求,算法将非常简单。首先遍历一次数组a,计算数组a中所有元素的乘积,并保存到一个临时变量tmp中,然后再遍历一次数组a并给数组赋值:b[i]=tmp/a[i]。但是这种方法使用了一个临时变量,不满足题目的要求。下面介绍另外一种方法。
在计算b[i]的时候,只要将数组a中除了a[i]以外的所有值相乘即可。这种方法的主要思路为:首先遍历一次数组a,在遍历的过程中对数组b进行赋值,b[i]=a[i-1]×b[i-1],这样经过一次遍历后,数组b的值为b[i]=a[0]×a[1]×...×a[i-1]。此时只需要将数组中的值b[i]再乘以a[i+1]×a[i+2]×...×a[N-1],实现方法为逆向遍历数组a,把数组后半段值的乘积记录到b[0]中,通过b[i]与b[0]的乘积就可以得到满足题目要求的b[i]。具体而言,先执行b[i]=b[i]×b[0](执行的目的是保证在执行下面一个计算的时候,b[0]中不包含与b[i]的乘积),接着记录数组后半段的乘积到b[0]中,即b[0]=b[0]×a[i]。实现代码如下所示。
function calculate(a,b,N){
b[0]=1;
for (var i=1;i<N;++i){ //正向计算乘积
b[i]=a[i-1]*b[i-1];
}
b[0]=a[N-1];
for(i=N-2;i>=1;--1){ //逆向计算乘积
b[i]*=b[0];
b[0]*=a[i];
}
}
var N=10,
a=[1,2,3,4,5,6,7,8,9,10],
b=[];
calculate(a,b,N);
console.log(b.join(""));
程序的运行结果为:
362880018144001209600 907200725760 604800 518400 453600 403200 362880
[考点] 数组
12. 给定以非递减顺序排序的三个数组,找出这三个数组中的所有公共元素。例如,给出下面三个数组:ar1=[2,5,12,20,45,85],ar2=[16,19,20,85,200],ar3=[3,4,15,20,39,72,85,190]。那么这三个数组的公共元素为{20,85}。
最容易想到的方法是首先找出两个数组的交集,然后再把这个交集存储在一个临时数组中,最后再找出这个临时数组与第三个数组的交集。这个算法的时间复杂度为O(N1+N2+N3),其中N1、N2和N3分别为三个数组的长度。这种方法不仅需要额外的存储空间,而且还需要额外的两次循环遍历。下面介绍另外一种只需要一次循环遍历、而且不需要额外存储空间的方法,主要思路如下。
假设当前遍历的三个数组元素分别为ar1[i]、ar2[j]和ar3[k],则存在以下几种可能性:
(1)如果ar1[i]、ar2[j]和ar3[k]相等,那么说明当前遍历的元素是三个数组的公有元素,可以直接打印出来,然后通过执行i++,j++,k++,使三个数组同时向后移动,此时继续遍历各数组后面的元素。
(2)如果ar1[i]<ar2[j],则执行i++来继续遍历ar1后面的元素,因为ar1[i]不可能是三个数组公有的元素。
(3)如果ar2[j]<ar3[k],同理可以通过j++来继续遍历ar2后面的元素。
(4)如果前面的条件都不满足,说明ar1[i]>ar2[j]且ar2[j]>ar3[k],此时可以通过k++来继续遍历ar3后面的元素。
实现代码如下所示。
function findCommon(ar1, ar2, ar3) {
var i=0,j=0,k=0,
n1=ar1.length,
n2=ar2.length,
n3=ar3.length,
share="";
//遍历三个数组
while(i<n1 && j<n2 && k<n3){
if(arl[i]==ar2[j]&& ar2[j]==ar3[k]){ //找到公有元素就保存
share+=ar1[i]+"";
i++;
j++;
k++;
}
else if(ar1[i]<ar2[j]) //ar1[i]不可能是共有的元素
i++;
else if(ar2[j]<ar3[k]) //ar2[j]不可能是共有的元素
j++;
else //ar3 [k]不可能是共有的元素
k++;
}
console.log(share);
}
[考点] 数组
13. 函数声明和函数表达式有哪些区别?
函数通常有2种创建方式:函数声明和函数表达式。它们的区别如下所列:
(1)函数声明必须包含名称,而函数表达式可省略名称。
(2)函数声明有位置限制,不能出现在条件语句、循环语句或其他语句中,而函数表达式没有位置限制,可以出现在语句中实现动态编程。
(3)函数声明会先于函数表达式被提升至作用域的顶部,因此用函数声明创建的函数可以在声明之前被调用,而函数表达式必须在表达式之后才能被调用。
[考点] 函数
14. 用递归实现一个简单的函数,返回一个布尔值,检测某个字符串是否是回文,例如“aabaa”返回true,而“aabcc”返回false。
当函数反复调用自身时,就执行了递归(recursion)操作。如果把一个字符串反转,能和原字符串相等,那么这就是一个回文字符串。下面代码就是实现回文验证功能的函数,它有两个结束递归的出口,如果不满足退出的条件,那么每次都会去除首尾字符再执行递归。
function palindrome(str){
if(str.length<=1)
return true;
//首字符和末字符做匹配
if(str[0]!=str[str.length-1])
return false;
//将去除首尾字符的字符串传入函数自身中
return palindrome(str.substr(1, str.length-2));
}
[考点] 函数
15. Function构造器有哪些功能?
利用Function构造器能创建函数,这是第三种创建函数的方式。构造器通过动态编译字符串代码来实现函数的创建,其实现方式和使用全局函数eval()类似。构造函数Function()能接收任意多个实参(在调用函数时传入的参数),最后一个参数是新函数的函数体,其他都是新函数的形参(在函数中定义的参数),下面代码演示了它的用法。
var func=new Function("a","b","return a+b;");
//相当于下面的函数表达式
var func=function(a,b){
return a+b;
};
用Function构造器创建新函数不但写法比较晦涩、性能比较低效,而且新函数使用的还是全局作用域,代码如下所示。
var name="freedom"; //全局变量
function func() {
var name="strick";
return new Function("return name;");
}
func()(); //"freedom"
[考点] 函数
16. 执行下面的代码,为何输出的都是3?
for (var i=0; i<3; 1++) {
setTimeout(function() {
console.log(i);
},0);
}
当把匿名函数作为值传递给定时器时,只要执行异步回调,就会创建一个闭包。在闭包中能够引用循环中的i变量,几个定时器都会在循环结束后再执行,此时i变量中的值为3。
[考点] 函数
17. 请谈谈你对闭包的理解。
当一个函数能够访问和操作另一个函数作用域中的变量时,就构成了一个闭包(closure)。闭包之所以有这个能力,是因为这些变量存在于该函数声明时所处的作用域。在一个函数中嵌套另一个函数,或者将一个匿名函数作为值传入另一个函数中,是创建闭包的常见方式。闭包有个最大的特点,就是能记住声明时所处的作用域,这样就能让函数在其他作用域中也能被成功调用,即使那个作用域消失了,它还是能访问其中的变量,因为它保存了变量的引用。
[考点] 函数
18. 请封装一个函数,用于判断某个数是否是质数。
质数又叫素数,是指一个大于1的自然数,除了1和它本身外,不能被其他自然数整除。利用记忆函数,可在每次计算完成后,就将计算结果缓存到函数的自有属性内,具体实现如下所示。
function prime(number) {
if(!prime.digits) {
prime.digits={}; //缓存对象
}
if(prime.digits[number]!==undefined) {
return prime.digits[number];
}
var isPrime=false;
for (var i=2; i<number; i++) {
if (number% i==0) {
isPrime=false;
break;
}
}
if (i=number){
isPrime=true;
}
return (prime.digits[number]=isPrime);
}
[考点] 函数
19. 编写一个add()函数,能正常执行下面的代码,并且能在控制台输出注释中的数字。
console.log(add(1, 2)); //3
console.log(add(1, 2, 3)); //6
console,log(add(1)(2)); //3
console.log(add(1)(2)(3)); //6
此题有两个特点,第一个是实参数量不定;第二个是用到了柯里化。柯里化(currying)也叫不完全函数(partial function),是一种部分求值的技术,能把一个完整的函数调用分解成多次函数调用,每次只传入部分参数,返回一个接受剩下参数的函数,如此循环往复,直至将所有参数传递过去,最后得出结果。
第一个特点可以通过函数内部的一个特殊对象arguments实现,这是一个类数组对象,管理着实参列表,通过数字索引就能从arguments对象中读取到未命名的实参。第二个特点需要在函数内部再定义一个函数,在这个内部函数中合并每次传入的实参,并返回自身。然后重写该内部函数的toString()方法,因为每次调用它都会返回其自身,而最后一次输出它(即执行console.log())的时候就会调用toString()方法,此时返回计算出的和,就能实现要求的效果,具体代码如下所示。
function add(){
var tmpSlice=[].slice,
params=tmpSlice.apply(arguments); //间接调用slice()方法
function currying(){
var arr=tmpSlice.apply(arguments);
//由于闭包的关系,所以能读取params变量
params=params.concat(arr); //合并参数
return currying;
}
currying.toString=function(){
var result=0;
params.forEach(function(value){
result+=value; //将所有参数相加
});
return result;
};
return currying;
}
[考点] 函数
20. 什么是事件循环?
运行在宿主环境中的JavaScript引擎针对单线程,提供了一种机制来更高效地处理多个任务,这个机制称为事件循环(Event Loop)。事件循环具体的执行过程分为以下4步:
(1)先执行主线程中的任务。
(2)当主线程空闲时,再从任务队列中读取任务,继续执行。
(3)即使主线程阻塞了,任务队列还是会继续接收任务。
(4)主线程不断地重复第(2)步操作。
[考点] BOM和DOM