Skip to content

Latest commit

 

History

History
6333 lines (4803 loc) · 156 KB

File metadata and controls

6333 lines (4803 loc) · 156 KB

1. 如何获取浏览器 URL 中查询字符串中的参数?

参考答案:

方法一:(基础版)

function getQueryString() {
    var sHref = window.location.href;
    var args = sHref.split("?");
    if (args[0] == sHref) {
        // 没有参数,直接返回空即可
        return "";
    }
    var arr = args[1].split("&");
    var obj = {};
    for (var i = 0; i < arr.length; i++) {
        var arg = arr[i].split("=");
        obj[arg[0]] = arg[1];
    }
    return obj;
}
var href = getQueryString();
console.log(href["categoryId"]);

方法二:(正则版, URL 存在#则不适用)

function getQueryString(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var r = window.location.search.substr(1).match(reg);
    if (r != null) return unescape(r[2]);
    return null;
}
console.log(getQueryString("categoryId"));

方法三:(正则升级版)

function getQueryString(name) {
    // 未传参,返回空
    if (!name) return null;
    // 查询参数:先通过search取值,如果取不到就通过hash来取
    var after = window.location.search;
    after = after.substr(1) || window.location.hash.split("?")[1];
    // 地址栏URL没有查询参数,返回空
    if (!after) return null;
    // 如果查询参数中没有"name",返回空
    if (after.indexOf(name) === -1) return null;
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    // 当地址栏参数存在中文时,需要解码,不然会乱码
    var r = decodeURI(after).match(reg);
    // 如果url中"name"没有值,返回空
    if (!r) return null;
    return r[2];
}
console.log(getQueryString("categoryId"));

参与互动

2. js 实现一个打点计时器

问题描述:

1、从 start 到 end(包含 start 和 end),每隔 100 毫秒 console.log 一个数字,每次数字增幅 1 2、返回的对象中需要包含一个 cancel 方法,用于停止定时操作 3、第一个数需要立即输出

参考答案:

// 实现法一(setTimeout()方法):

function count(start, end) {
    if (start <= end) {
        console.log(start++);
        st = setTimeout(function() {
            count(start, end);
        }, 100);
    }
    return {
        cancel: function() {
            clearTimeout(st);
        }
    };
}
count(1, 10);

// 实现法二(setInterval()方法):

function count(start, end) {
    console.log(start++);
    var timer = setInterval(function() {
        if (start <= end) {
            console.log(start++);
        }
    }, 100);
    return {
        cancel: function() {
            clearInterval(timer);
        }
    };
}
count(1, 10);

知识点: setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式。 语法:setTimeout(code, millisec) 注意:setTimeout() 只执行 code 一次。如果要多次调用,请使用 setInterval() 或者让 code 自身再次调用 setTimeout()。

setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。 语法:setInterval(code , millisec[, "lang"]) setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。

参与互动

3. 用 js 实现一个标准的排序算法

参考答案:

一. 冒泡排序

它是最慢的排序算法之一,但也是一种最容易实现的排序算法。 之所以叫冒泡排序是因为使用这种排序算法排序时,数据值会像气泡一样从数组的一端漂浮到另一端。假设正在将一组数字按照升序排列,较大的值会浮动到数组的右侧,而较小的值则会浮动到数组的左侧。之所以会产生这种现象是因为算法会多次在数组中移动,比较相邻的数据,当左侧值大于右侧值时将它们进行互换。

function BubbleSort(array) {
    var length = array.length;
    for (var i = length - 1; i > 0; i--) {
        //用于缩小范围
        for (var j = 0; j < i; j++) {
            //在范围内进行冒泡,在此范围内最大的一个将冒到最后面
            if (array[j] > array[j + 1]) {
                var temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
        console.log(array);
        console.log("-----------------------------");
    }
    return array;
}

var arr = [10, 9, 8, 7, 7, 6, 5, 11, 3];
var result = BubbleSort(arr);
console.log(result);
/*
[ 9, 8, 7, 7, 6, 5, 10, 3, 11 ]
-----------------------------
[ 8, 7, 7, 6, 5, 9, 3, 10, 11 ]
-----------------------------
[ 7, 7, 6, 5, 8, 3, 9, 10, 11 ]
-----------------------------
[ 7, 6, 5, 7, 3, 8, 9, 10, 11 ]
-----------------------------
[ 6, 5, 7, 3, 7, 8, 9, 10, 11 ]
-----------------------------
[ 5, 6, 3, 7, 7, 8, 9, 10, 11 ]
-----------------------------
[ 5, 3, 6, 7, 7, 8, 9, 10, 11 ]
-----------------------------
[ 3, 5, 6, 7, 7, 8, 9, 10, 11 ]
-----------------------------
[ 3, 5, 6, 7, 7, 8, 9, 10, 11 ]
*/

二. 选择排序

选择排序从数组的开头开始,将第一个元素和其他元素进行比较。检查完所有元素后,最小的元素会被放到数组的第一个位置,然后算法会从第二个位置继续。这个过程一直进行,当进行到数组的倒数第二个位置时,所有数据便完成了排序。

function SelectionSort(array) {
    var length = array.length;
    for (var i = 0; i < length; i++) {
        //缩小选择的范围
        var min = array[i]; //假定范围内第一个为最小值
        var index = i; //记录最小值的下标
        for (var j = i + 1; j < length; j++) {
            //在范围内选取最小值
            if (array[j] < min) {
                min = array[j];
                index = j;
            }
        }
        if (index != i) {
            //把范围内最小值交换到范围内第一个
            var temp = array[i];
            array[i] = array[index];
            array[index] = temp;
        }
        console.log(array);
        console.log("---------------------");
    }
    return array;
}

var arr = [1, 10, 100, 90, 65, 5, 4, 10, 2, 4];
var result = SelectionSort(arr);
console.log(result);
/*
[ 1, 10, 100, 90, 65, 5, 4, 10, 2, 4 ]
---------------------
[ 1, 2, 100, 90, 65, 5, 4, 10, 10, 4 ]
---------------------
[ 1, 2, 4, 90, 65, 5, 100, 10, 10, 4 ]
---------------------
[ 1, 2, 4, 4, 65, 5, 100, 10, 10, 90 ]
---------------------
[ 1, 2, 4, 4, 5, 65, 100, 10, 10, 90 ]
---------------------
[ 1, 2, 4, 4, 5, 10, 100, 65, 10, 90 ]
---------------------
[ 1, 2, 4, 4, 5, 10, 10, 65, 100, 90 ]
---------------------
[ 1, 2, 4, 4, 5, 10, 10, 65, 100, 90 ]
---------------------
[ 1, 2, 4, 4, 5, 10, 10, 65, 90, 100 ]
---------------------
[ 1, 2, 4, 4, 5, 10, 10, 65, 90, 100 ]
---------------------
[ 1, 2, 4, 4, 5, 10, 10, 65, 90, 100 ]
*/

三. 插入排序

插入排序有两个循环。外循环将数组元素挨个移动,而内循环则对外循环中选中的元素进行比较。如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。

function InsertionSort(array) {
    var length = array.length;
    for (var i = 0; i < length - 1; i++) {
        //i代表已经排序好的序列最后一项下标
        var insert = array[i + 1];
        var index = i + 1; //记录要被插入的下标
        for (var j = i; j >= 0; j--) {
            if (insert < array[j]) {
                //要插入的项比它小,往后移动
                array[j + 1] = array[j];
                index = j;
            }
        }
        array[index] = insert;
        console.log(array);
        console.log("-----------------------");
    }
    return array;
}

var arr = [100, 90, 80, 62, 80, 8, 1, 2, 39];
var result = InsertionSort(arr);
console.log(result);
/*
[ 90, 100, 80, 62, 80, 8, 1, 2, 39 ]
-----------------------
[ 80, 90, 100, 62, 80, 8, 1, 2, 39 ]
-----------------------
[ 62, 80, 90, 100, 80, 8, 1, 2, 39 ]
-----------------------
[ 62, 80, 80, 90, 100, 8, 1, 2, 39 ]
-----------------------
[ 8, 62, 80, 80, 90, 100, 1, 2, 39 ]
-----------------------
[ 1, 8, 62, 80, 80, 90, 100, 2, 39 ]
-----------------------
[ 1, 2, 8, 62, 80, 80, 90, 100, 39 ]
-----------------------
[ 1, 2, 8, 39, 62, 80, 80, 90, 100 ]
-----------------------
[ 1, 2, 8, 39, 62, 80, 80, 90, 100 ]
*/

四. 希尔排序

希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。

function shellSort(arr) {
  let len = arr.length;
  // gap 即为增量
  for (let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
    for (let i = gap; i < len; i++) {
      let j = i;
      let current = arr[i];
      while(j - gap >= 0 && current < arr[j - gap]) {
        arr[j] = arr[j - gap];
        j = j - gap;
      }
      arr[j] = current;
    }
    console.log(arr);
    console.log("-----------------------");
  }
}

var arr = [3,5,7,1,4,56,12,78,25,0,9,8,42,37];
shellSort(arr);

/*
[3, 5, 0, 1, 4, 42, 12, 78, 25, 7, 9, 8, 56, 37]
-----------------------
[1, 4, 0, 3, 5, 8, 7, 9, 25, 12, 37, 42, 56, 78]
-----------------------
[0, 1, 3, 4, 5, 7, 8, 9, 12, 25, 37, 42, 56, 78]
-----------------------
*/

五. 归并排序

归并排序把一系列排好序的子序列合并成一个大的完整有序序列。我们需要两个排好序的子数组,然后通过比较数据大小,从最小的数据开始插入,最后合并得到第三个数组。然而,在实际情况中,归并排序还有一些问题,我们需要更大的空间来合并存储两个子数组。

var array = [3,5,7,1,4,56,12,78,25,0,9,8,42,37];
console.log(array);
console.log("-----------------------");
var len = array.length;
sort(0,len);
console.log(array);
function sort(begin,end) {
    if (end - begin < 2) {
        return;
    }
    let mid = (begin + end) >> 1;
    sort(begin, mid);
    sort(mid, end);
    merge(begin,mid,end);
}
function merge(begin,mid,end) {
    let li = 0,le = mid - begin;
    let ri = mid,re = end;
    let ai = begin;
    let leftArray = [];
    for (let i = li;i<le;i++) {
        leftArray[i] = array[begin + i];
    }
    while(li < le){
        if(ri < re && array[ri] < leftArray[li]){
            array[ai++] = array[ri++];
        }else{
            array[ai++] = leftArray[li++];
        }
    }
}
/*
[3, 5, 7, 1, 4, 56, 12, 78, 25, 0, 9, 8, 42, 37]
-----------------------
[0, 1, 3, 4, 5, 7, 8, 9, 12, 25, 37, 42, 56, 78]
*/

六. 快速排序

快速排序是处理大数据集最快的排序算法之一。它是一种分而治之的算法,通过递归的方法将数据依次分解为包含较小元素和较大元素的不同子序列。该算法不断重复这个步骤直到所有数据都是有序的。 这个算法首先要在列表中选择一个元素作为基准值(pivot)。数据排序围绕基准值进行,将列表中小于基准值的元素移到数组的底部,将大于基准值的元素移到数组的顶部。

function quickSort(arr, i, j) {
  if(i < j) {
    let left = i;
    let right = j;
    let pivot = arr[left];
    while(i < j) {
      while(arr[j] >= pivot && i < j) {  // 从后往前找比基准小的数
        j--;
      }
      if(i < j) {
        arr[i++] = arr[j];
      }
      while(arr[i] <= pivot && i < j) {  // 从前往后找比基准大的数
        i++;
      }
      if(i < j) {
        arr[j--] = arr[i];
      }
    }
    arr[i] = pivot;
    quickSort(arr, left, i-1);
    quickSort(arr, i+1, right);
    return arr;
  }
}

let arr = [2, 10, 4, 1, 0, 9, 5 ,2];
console.log(quickSort(arr, 0 , arr.length-1));
/*
[0, 1, 2, 2, 4, 5, 9, 10]
*/

参与互动

4. 正则表达式,验证手机号码,验证规则:11 位数字,以 1 位开头

参考答案:

checkphonenumber(number) {
    if (number == null || number.length != 11) {
        return false
    } else {
        // 移动号段正则表达式
        var pat1 = '^((13[4-9])|(147)|(15[0-2,7-9])|(178)|(18[2-4,7-8]))\\d{8}|(1705)\\d{7}$';
        // 联通号段正则表达式
        var pat2 = '^((13[0-2])|(145)|(15[5-6])|(176)|(18[5,6]))\\d{8}|(1709)\\d{7}$';
        // 电信号段正则表达式
        var pat3 = '^((133)|(153)|(177)|(18[0,1,9])|(149))\\d{8}$';
        // 虚拟运营商正则表达式
        var pat4 = '^((170))\\d{8}|(1718)|(1719)\\d{7}$';
        if (!part1.test(number)) {
            return false
        }
        if (!part2.test(number)) {
            return false
        }
        if (!part3.test(number)) {
            return false
        }
        if (!part4.test(number)) {
            return false
        }
    }
    return true
}

参与互动

5. 请给 Array 本地对象增加一个原型方法,用于删除数组中重复的条目并按升序排序,返回值是被删除条目的新数组

参考答案:

Array.prototype.distinct = function() {
    var ret = [];
    for (var i = 0; i < this.length; i++) {
        for (var j = i + 1; j < this.length;) {
            if (this[i] === this[j]) {
                ret.push(this.splice(j, 1)[0]);
            } else {
                j++;
            }
        }
    }
    return ret;
};
let arr = ["a", "b", "c", "d", "b", "a", "e"];
console.log(arr.distinct()); // ['a', 'b']
console.log(arr); // ['a', 'b', 'c', 'd', 'e']

参与互动

6. 为字符串扩展一个 rewrite 函数,接收一个正则 pattern 和一个字符串 result, 如果该字符串符合 pattern, 则以 result 对结果进行转义输出。

参考答案:

"/foo".rewrite(/^\/foo/, "/bar");
"u1234".rewrite(/^\/u(\d+)/, "/user/$1");
"/i".rewrite(/^\o/, "/ooo");

参与互动

7. 实现一个 js 对象序列化函数,将 js 对象序列化为可反序列化的代码,要求 1.尽量和 json 兼容,2.支持不可序列化的值,如 undefined/NaN/Infinify-Infinity,3.支持特殊对象,如正则、Date 等

参考答案:

serialize({});
serialize({
    a: "b"
});
serialize({
    a: 0 / 0
});
serialize({
    a: /foo/
});

参与互动

8. 设计一道 JavaScript 的 range 算法如下:

range(1, 10, 3) 返回 [1, 4, 7, 10]; range('A', 'F', 2) 返回 ['A', 'C', 'E']; 请使用 JavaScript 语言实现该功能(可以使用 ES6)

参考答案:

function range() {
    var args = [].slice.call(arguments); // 相当于Array.slice.call(arguments),目的是将arguments对象的数组提出来转化为数组,arguments本身并不是数组而是对象
    var str = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    var result = [];
    if (args.length > 2) {
        if (typeof args[0] === 'number') { // 如果是number型数据
            for (var i = args[0]; i <= args[1]; i = i + args[2]) {
                result.push(i);
            }
        } else {
            for (var i = str.indexOf(args[0]); i <= str.indexOf(args[1]); i = i + args[2]) {
                result.push(str[i]);
            }
        }
    }
    return result;
}

range(1, 10, 3); //  [1, 4, 7, 10]
// range('A', 'F', 2); // ['A', 'C', 'E']

参与互动

9. 头条的视频网站上支持了弹幕,假设一个视频有很多弹幕,弹幕的数据是一个数组,格式定义如下:


[
    {
        time: Number,
        content: String
    },
    {
        time: Number,
        content: String
    }...
]
(其中 time 表示时间,content表示弹幕内容),那么如何快速定位到某个时间点的弹幕,请编码实现(不使用数组的 sort 方法)

参考答案:

参与互动

10. 请写出以下代码的执行结果

(function() {
    fn();
    var fn = function() {
        alert(1);
    }
    fn();
    function fn() {
        alert(2)
    }
})()

参考答案:

第一次弹出2,第二个弹出1

// 变量提升之后的代码:
(function() {
    function fn() {
        alert(2)
    }
    var fn;
    fn();
    fn = function() {
        alert(1);
    }
    fn();
})()

参与互动

11. 请说明以下各种情况的执行结果,并注明产生对应结果的理由

function doSomething() {
    alert(this);
}

a) element.onclick = doSomething, 点击 element 元素后
b) element.onclick = function() doSomething(){}, 点击 element 元素后
c) 直接执行 doSomething()

参考答案:

参与互动

12. 请写出以下代码的执行结果

例1:

var obj = new Object();
var events = {m1: 'clicked', m2: 'changed'};

for (var e in events) {
    (function() {
        var aValue = e;
        obj[e] = function() {
            // var aValue = e;
            console.log(events[aValue]);
        };
    }());
};

console.log(obj.m1 === obj.m2); // false

obj.m1(); // clicked
obj.m2(); // changed

例2:

var obj = new Object();
var events = {m1: 'clicked', m2: 'changed'};

for (var e in events) {
    (function() {
        // var aValue = e;
        obj[e] = function() {
            var aValue = e;
            console.log(events[aValue]);
        };
    }());
};

console.log(obj.m1 === obj.m2); // false

obj.m1(); // changed
obj.m2(); // changed

参考答案:

例1:false clicked changed 例2:false changed changed

解析:

以上两个例子中,除了var aValue = e;这一句位置不同:例1位于外层匿名函数中、例2位于内层匿名函数中,其他部分完全相同。为什么结果不同?

例1:for 循环进行的过程中,就把当时的 e 像拍照一样封存在了aValue变量里(注意,这里每一次循环都产生了一个新的闭包,所以循环了几次就有几个aValue同时存在,本例是2个,它们的值分别是'm1' 和 'm2'),当你调用obj.m1() 时,取的是闭包中的aValue,而不是现在的 e 了。

例2:内层函数obj.m1和obj.m2是在循环结束后才执行的,此时循环变量e的值为'm2'(注意 e 是 for 循环的循环变量,而当你调用 obj.m1() 和 obj.m2()的时候,for循环早已结束了,因此它的循环变量 e 已经永远地停留在了 'm2'),因此obj.m1和obj.m2中的局部变量aValue的值只能是'm2'。

参与互动

13. 请写出类 Son 继承类 Father

function Father() {} function Son() {}

参考答案:

参与互动

14. 请用 JS 写出一个遍历 DOM 节点树的方法

参考答案:

参与互动

15. 尝试实现注释部分的 JavaScript 代码, 可在其他任何地方添加更多代码。

var Obj = function(msg) {
    this.msg = msg;
    this.shout = function () {
        alert(this.msg)
    }
    this.waitAndShout = function() {
        // 隔五秒钟后执行上面的 shout 方法
    }
}

参考答案:

参与互动

16. 请编写一个 JavaScript 函数 parseQuerySting, 它的用途是把 URL 参数解析为一个对象,如

var url = "http://www.58.com/index.aspx?key0=0&key1=1&key2=2..."
var obj = parseQuerySting(url);
alert(obj.key0) // 输出 0

参考答案:

参与互动

17. 请给 Array 本地对象添加一个原型方法,它用于删除数组条目中重复的条目(可能有多个重复),返回值是一个包含被删除的重复条目的新数组

参考答案:

参与互动

18. 我们把一个数字倒着读和原数字相同的数字称之为对称数,例如(1, 121, 88, 8998), 不考虑性能,请找出 1 - 10000 之间的对称数,要求用 JS 实现

参考答案:

function findSymmetryNum(s, o) {
	var arr = [];
	for (var i = s; i <= o; i++) {
		var str = '' + i,
		sl = str.length,
		middle = 0,
		flag = true;
        // 字符串分割成两半,记录中间数值middle
		if (sl % 2 === 0) {
			middle = sl / 2;
		} else {
			middle = (sl - 1) / 2;
		}
        // 判断middle左右的数是否对称
		for (var m = 0; m < middle; m++) {
			if (str.substr(0 + m, 1) !== str.substr( - 1 - m, 1)) {
				flag = false;
			}
		}
		flag && arr.push(i);
	}
	console.log(arr);
	return arr;
}
findSymmetryNum(1, 10000);

参与互动

19. 以下代码输出多少

var name = "world";
(function() {
    if (typeof name === "undefined") {
        var name = "jack";
        console.log("Hi!" + name);
    } else {
        console.log("Hello," + name)
    }
})()

==
>
Hi!jack

var name = "world";
(function(name) {
    if (typeof name === "undefined") {
        var name = "jack";
        console.log("Hi!" + name);
    } else {
        console.log("Hello," + name)
    }
})(name)

==
>
Hello, world

参与互动

20. js 数组拍平(数组扁平化)的六种方式

参考答案:

  1. 数组拍平也称数组扁平化,就是将数组里面的数组打开,最后合并为一个数组

  2. 实现

var arr = [1, 2, [3, 4, 5, [6, 7, 8], 9], 10, [11, 12]];

a:递归实现

function fn(arr) {
    let arr1 = [];
    arr.forEach(val => {
        if (val instanceof Array) {
            arr1 = arr1.concat(fn(val));
        } else {
            arr1.push(val);
        }
    });
    return arr1;
}

b:reduce 实现

function fn(arr) {
    return arr.reduce((prev, cur) => {
        return prev.concat(Array.isArray(cur) ? fn(cur) : cur);
    }, []);
}

c:flat

参数为层数(默认一层)

arr.flat(Infinity);

d:扩展运算符

function fn(arr) {
    let arr1 = [];
    let bStop = true;
    arr.forEach(val => {
        if (Array.isArray(val)) {
            arr1.push(...val);
            bStop = false;
        } else {
            arr1.push(val);
        }
    });
    if (bStop) {
        return arr1;
    }
    return fn(arr1);
}

e:toString

let arr1 = arr
    .toString()
    .split(",")
    .map(val => {
        return parseInt(val);
    });
console.log(arr1);

f:apply

function flatten(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat.apply([], arr);
    }
    return arr;
}

参与互动

21. 如何解决数组塌陷问题

什么叫数组塌陷?

一个数组在进行删除数据单元操作的时候,删除掉这个单元之后,后面的数据单元会自动的补充的这个位置上来,造成数组长度的减少,这种情况被称之为数组塌陷。

例:循环删除数组中的数据,每循环一次,删除一个数据单元

var arr = [0,1,2,3,4,5,6,7,8,9];
for (var i = 0; i < arr.length; i++) {
    arr.splice(i, 1);
}
console.log(arr) // [1, 3, 5, 7, 9]

那么 现在的输出是 [1, 3, 5, 7, 9] 还剩 5 个 单元 ,也就是还有一半没有删除。

因为:我们使用for 循环遍历 arr, 当i = 0的时候, 我们删除了位置为 0 的元素,此时位置为 1 的元素接替了位置 0 , 但同时 i 也累加了, 下次执行删除操作的时候 i 变为 1,再次执行删除操作,其实是删除了现在位置为 1 的元素, 中间跳过了, 所以最后的结果只删除了一半。

如何解决数组塌陷问题呢?

参考答案:

// 方法1 使用i--
for (var i = 0; i < arr.length; i++) {
    arr.splice(i, 1);
    i--;
}
console.log(arr); // []

// 方法2 从数组的末尾一项开始遍历
for (var i = arr.length - 1; i >= 0; i--) {
    arr.splice(i, 1);
}
console.log(arr); // []

参与互动

22. 大部分人都会做错的经典 JS 闭包面试题

function fun(n,o) {
  console.log(o)
  return {
       fun:function(m){
        return fun(m,n);
      }
  };
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3); // undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3); // undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3); // undefined,?,?,?

问:三行a,b,c的输出分别是什么?

这是一道非常典型的JS闭包问题。其中嵌套了三层fun函数,搞清楚每层fun的函数是那个fun函数尤为重要。

可以先在纸上或其他地方写下你认为的结果,然后展开看看正确答案是什么?

参考答案: a: undefined,0,0,0 b: undefined,0,1,2 c: undefined,0,1,1

1、第一行a

var a = fun(0); a.fun(1); a.fun(2); a.fun(3);   可以得知,第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。

遂:在第一次调用fun(0)时,o为undefined;第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0;第四次同理;

即:最终答案为undefined,0,0,0

2、第二行b

var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,? 先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。

遂:在第一次调用第一层fun(0)时,o为undefined;第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;

即最终答案:undefined,0,1,2

3、第三行c

var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,? 根据前面两个例子,可以得知:fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。

遂:在第一次调用第一层fun(0)时,o为undefined;第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1

即最终答案:undefined,0,1,1

解析:参考

参与互动

23. 编写一个数组去重的方法

参考答案:

1、利用ES6 Set去重(ES6中最常用)

var arr = [1,1,8,8,12,12,15,15,16,16];
function unique (arr) {
  return Array.from(new Set(arr))
}

console.log(unique(arr)) //[1,8,12,15,16]

不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

2、利用for嵌套for,然后splice去重(ES5中最常用)

var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16];

function unlink(arr) {
    for (var i = 0; i < arr.length; i++) {    // 首次遍历数组
        for (var j = i + 1; j < arr.length; j++) {   // 再次遍历数组
            if (arr[i] == arr[j]) {          // 判断连个值是否相等
                arr.splice(j, 1);           // 相等删除后者
                j--;
            }
        }
    }
    return arr
}
console.log(unlink(arr));
// NaN和{}没有去重,两个null直接消失了

双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。

3、利用indexOf去重

var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16];
function unlink(arr) {
    if (!Array.isArray(arr)) {
        console.log('错误!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {    // 首次遍历数组
        if (array.indexOf(arr[i]) === -1) {   // 判断索引有没有等于
            array.push(arr[i])
        }
    }
    return array
}
console.log(unlink(arr));
// NaN、{}没有去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

4、利用includes

var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16];
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
        if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
            array.push(arr[i]);
        }
    }
    return array
}
console.log(unique(arr))

5、利用filter

var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16];
function unlink(arr) {
    return arr.filter(function (item, index, arr) {
        //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
        return arr.indexOf(item, 0) === index;
    });
}
console.log(unlink(arr));

参与互动

24. 已知 id 的 input 输入框,希望获取这个输入框的输入值,怎么做?(不使用第三方框架)

参考答案:

document.getElementById("id").value;

参与互动

25. 获取到页面中所有的 checkbox 怎么做?(不使用第三方框架)

参考答案:

var domList = document.getElementsByTagName("input");
var ckList = []; // 返回的所有的 checkbox
var len = domList.length;
for (var i = 0; i < len; i++) {
    if (domList[i].type == "checkbox") {
        ckList.push(domList[i]);
    }
}
console.log(ckList)

参与互动

26. 设置一个已知 id 的 div 的 html 内容为 xxxx,字体颜色设置为黑色(不使用第三方框架)

参考答案:

var dom = document.getElementById("id");
dom.innerHTML = "xxxx";
dom.style.color = "#000"; // 'black'

参与互动

27. JavaScript 中的相等性判断

参考答案:

2 == true[] == false[] == ![]

解析:参考

参与互动

28. 已知有字符串 foo="get-element-by-id", 写一个 function 将其转化为驼峰表示法"getElementById"

参考答案:

var string = "get-element-by-id";

function combo(msg) {
    var arr = msg.split("-"); //split("-")以-为分隔符截取字符串,返回数组
    for (var i = 1; i < arr.length; i++) {
        arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
    }
    msg = arr.join(""); //join()返回字符串
    return msg;
}
console.log(combo(string));

参与互动

29. 看下面代码,如何输出1 2 3

for (var i = 1; i <= 3; i++) {
    console.log(i);
}
// 输出 1 2 3

但是

for (var i = 1; i <= 3; i++) {
    setTimeout(() => {
        // setTimout在for里面是异步执行的,在延迟输出的时候,i的值已经是4了
        console.log(i);
    }, 0);
}
// 输出 4 4 4

如何输出 1 2 3

参考答案:

  1. 立即执行函数
for (var i = 1; i <= 3; i++) {
    setTimeout(
        (i => {
            console.log(i);
        })(i),
        0
    );
}
  1. 闭包
for (var i = 1; i <= 3; i++) {
    setTimeout(
        (() => {
            var j = i;
            return function() {
                console.log(j);
            };
        })(),
        0
    );
}

参与互动

30. JS 字符串使用堆栈处理 "(a, b, (c, d), f, g)"

参考答案:

参与互动

31. 二维数组操作

参考答案:

参与互动

32. 用最简单的方式,求一个数组中最大的元素

例如 arr = [5, 7, 9, 42, 18, 29]

参考答案:

var a = [5, 7, 9, 42, 18, 29];
console.log(Math.max.apply(null, a)); // 42
console.log(Math.min.apply(null, a)); // 5

大家都知道,apply方法,第一个参数是对象this,第二个参数是一个数组集合。为什么在这里第一个参数是null?

那是因为没有对象去调用这个方法,只需要用这个方法运算,得到返回的结果就行了

参与互动

33. 写一个 function,清除字符串前后的空格(兼容所有的浏览器)

参考答案:

// 重写trim方法
if (!String.prototype.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s+/, "").replace(/\s+$/, "");
    };
}

// test the function
var str = " \t\n test string ".trim(); 
console.log(str == "test string"); // true

参与互动

34. 运算符面试题

var a = 10,
    b = 20,
    c = 30;
++a;
a++;
e = ++a + ++b + c++ + a++;
console.log(e);

参考答案:77

解析:

1:后置++ 是将自身的值赋给变量,之后自身再加1; 2:前置++ 是将自身+1 后的值赋给变量,同时自身加1;

e = 13 + 21 + 30 + 13

参与互动

35. this 面试题

参考答案:

 this指向了谁?
 看函数在执行的时候是如何调用的,
 1 如果这个函数是用普通函数调用模式来进行调用,它内部的this指向了window;
 2 如果一个函数在调用的时候是通过对象方法模式来进行调用,则它内部的this就是我们的对象;
 3 如果一个函数在调用的时候通过构造函数模式调用,则它内部的this指向了生成的实例;
 4 如果这个函数是通过方法借用模式调用,则这个函数内部的this就是我们手动指定this。
// 第1题
function Fn() {
    console.log(this);
}
Fn(); // window   普通函数调用模式
new Fn(); // {}   构造函数调用模式
Fn.apply(Fn); // Fn的函数体   方法借用模式

// 第2题
var o = {
    f: function() {
        console.log(this);
    },
    2: function() {
        console.log(this);
        console.log(this.__proto__ === o[2].prototype);
    }
};
o.f(); // o   对象调用模式
o[2](); // o   对象调用模式
new o[2](); // {}   通过构造函数模式进行调用
o.f.call([1, 2]); // [1,2]   call方法进行方法借用
o[2].call([1, 2, 3, 4]); // [1,2,3,4]   call方法进行方法借用

// 第3题
var name = "out";
var obj = {
    name: "in",
    prop: {
        name: "inside",
        getName: function() {
            return this.name;
        }
    }
};

console.log(obj.prop.getName()); // 对象调用模式来进行调用  obj.prop.name,打印 'inside'
var test = obj.prop.getName; // 把test这个变量指向了obj.prop.getName所在的内存地址
console.log(test()); // 普通函数模式来进行调用,this指向window,打印 'out'
console.log(obj.prop.getName.apply(window)); // 方法借用模式,打印 'out'
console.log(obj.prop.getName.apply(this)); // 方法借用模式,打印 'out'
console.log(this === window); // true

// 第4题
var length = 10;
function fn() {
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function(f) {
        console.log(this);
        f(); // f在调用的时候是什么调用模式?普通函数调用模式  window.length,打印 10

        arguments[0](); 
        // 通过什么模式来进行调用的。执行之前有[]和.就是对象调用模式。
        // arguments是一个类数组,也就是一个对象,就是通过arguments来进行调用的
        // 通过arguments对象进行调用,因此函数内部的this是 arguments
        // arguments.length实参的数量。实参长度是1,所以打印 1

        arguments[0].call(this);
        // 如果一个函数在调用的时候它前面有call和apply那么就肯定是方法借用模式调用
        // 调用method方法是通过obj.method 因此在这里的this就是 obj
        // 通过call方法把fn内的this指向了obj
        // 输出obj.length,打印 5
    }
};
obj.method(fn);

// 第5题
function Foo() {
    getName = function() {
        console.log(1);
    };
    return this;
}
Foo.getName = function() {
    console.log(2);
};
Foo.prototype.getName = function() {
    console.log(3);
};
var getName = function() {
    console.log(4);
};
function getName() {
    console.log(5);
}
// 请写出以下输出结果:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

// 第6题
var obj = {
    fn: function() {
        console.log(this);
    }
};
obj.fn(); // obj
var f = obj.fn;
f(); // window
console.log(f === obj.fn); // true
// f和obj.fn是同一个函数,但是他们在调用的时候使用的函数调用模式不同,因此,它们内部的this指向也就不同。

// 第7题
var arr = [
    function() {
        console.log(this);
    }
];
arr[0](); // 数组本身
// 数组也是一个复杂数据类型,也是一个对象,那用数组去调用函数,使用的模式就是对象方法调用模式。
function f() {
    console.log(this);
}
function fn() {
    console.log(arguments); // 类数组,也是就一个对象   [0:function f(){}]
    console.log(this); // window
    arguments[0]();
    console.log(arguments[0]); // 内部的this就是arguments
    // 通过arguments对f这个方法进行调用,使用的是对象方法调用模式。
}
fn(f);

// 第8题
function SuperClass() {
    this.name = "women";
    this.bra = ["a", "b"];
}
SuperClass.prototype.sayWhat = function() {
    console.log("hello");
};
function SubClass() {
    this.subname = "you sister";
    SuperClass.call(this);
}
var sub = new SubClass();
console.log(sub.sayWhat());

参与互动

36. 实现一个 new 操作符

参考答案:

function realizeNew () {
    //创建一个新对象
    let obj  = {};
    //获得构造函数
    let Con = [].shift.call(arguments);
    //链接到原型(给obj这个新生对象的原型指向它的构造函数的原型)
    obj.__proto__ = Con.prototype;
    //绑定this
    let result = Con.apply(obj,arguments);
    //确保new出来的是一个对象
    return typeof result === "object" ? result : obj
}

参考参与互动

37. 用js实现一个 call 或 apply 方法

参考答案:

  1. call
Function.prototype.call2 = function(context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for (var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    }

    var result = eval("context.fn(" + args + ")");

    delete context.fn;
    return result;
};
  1. apply
Function.prototype.apply2 = function(context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push("arr[" + i + "]");
        }
        result = eval("context.fn(" + args + ")");
    }

    delete context.fn;
    return result;
};

参与互动

38. 实现一个 Function.bind

参考答案:

Function.prototype.bind2 = function(context) {
    if (typeof this !== "function") {
        throw new Error(
            "Function.prototype.bind - what is trying to be bound is not callable"
        );
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function() {};
    var fbound = function() {
        self.apply(
            this instanceof self ? this : context,
            args.concat(Array.prototype.slice.call(arguments))
        );
    };
    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();
    return fbound;
};

参与互动

39. 实现一个继承

参考答案:

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayName = function() {
    console.log("parent name:", this.name);
};

function Child(name, parentName) {
    Parent.call(this, parentName);
    this.name = name;
}

function create(proto) {
    function F() {}
    F.prototype = proto;
    return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
    console.log("child name:", this.name);
};

Child.prototype.constructor = Child;
var parent = new Parent("汪某");
parent.sayName(); // parent name: 汪某
var child = new Child("son", "汪某");

参与互动

40. 手写一个 Promise(中高级必考)

参考答案:

function myPromise(constructor) {
    let self = this;
    self.status = "pending";
    //定义状态改变前的初始状态
    self.value = undefined;
    //定义状态为resolved的时候的状态
    self.reason = undefined;
    //定义状态为rejected的时候的状态
    function resolve(value) {
        //两个==="pending",保证了状态的改变是不可逆的
        if (self.status === "pending") {
            self.value = value;
            self.status = "resolved";
        }
    }

    function reject(reason) {
        //两个==="pending",保证了状态的改变是不可逆的
        if (self.status === "pending") {
            self.reason = reason;
            self.status = "rejected";
        }
    }
    //捕获构造异常
    try {
        constructor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

//同时,需要在 myPromise的原型上定义链式调用的 then方法:
myPromise.prototype.then = function(onFullfilled, onRejected) {
    let self = this;
    switch (self.status) {
        case "resolved":
            onFullfilled(self.value);
            break;
        case "rejected":
            onRejected(self.reason);
            break;
        default:
    }
};

//测试一下:
var p = new myPromise(function(resolve, reject) {
    resolve(1);
});
p.then(function(x) {
    console.log(x);
});

参与互动

41. 手写防抖(debounce)和节流(throttle)

参考答案:

// 防抖函数
function debounce(fn, wait) {
    let timer;
    return function() {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, wait);
    };
}

// (参考博客https://segmentfault.com/a/1190000018428170)
/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn, delay) {
    let timer = null // 借助闭包
    return function() {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(fn, delay)
    }
}
// 节流函数 通过时间戳差值是否大于指定间隔时间来做判定
function throttle(fn, wait) {
    let prev = new Date();
    return function() {
        const args = arguments;
        const now = new Date();
        if (now - prev > wait) {
            fn.apply(this, args);
            prev = new Date();
        }
    };
}

// 通过setTimeout的返回的标记当做判断条件实现(参考博客https://segmentfault.com/a/1190000018428170)
/*
* fn [function] 需要节流的函数
* delay [number] 毫秒,节流期限值
*/
function throttle(fn, delay) {
    let flag = true
    return function() {
        if (!flag) {
            // 休息时间 暂不接客
            return false
        }
        // 工作时间,执行函数并且在间隔期内把状态位设为无效
        flag = false
        setTimeout(() => {
            fn()
            flag = true;
        }, delay)
    }
}

参与互动

42. 手写一个 JS 深拷贝

参考答案:

function deepCopy(obj) {
    //判断是否是简单数据类型,
    if (typeof obj == "object") {
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for (let i in obj) {
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    } else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}
let o1 = {
    a: {
        b: 1
    }
};
let o2 = JSON.parse(JSON.stringify(o1));

另一种方法

function deepCopy(s) {
    const d = {};
    for (let k in s) {
        if (typeof s[k] == "object") {
            d[k] = deepCopy(s[k]);
        } else {
            d[k] = s[k];
        }
    }

    return d;
}

参与互动

43. 看下面代码,给出输出结果(考察闭包及++运算符)

参考答案:

function Foo() {
    var i = 0;
    return function() {
        console.log(i++);
    };
}
var f1 = Foo(),
    f2 = Foo();

f1(); // 0
f1(); // 1
f2(); // 0
function fn() {
    var a = 1;
    return function() {
        a++;
        console.log(a);
    };
}
var b = fn();
console.log(b());
// 2
function fn() {
    var a = 1;
    return function() {
        console.log(a++);
    };
}
var b = fn();
console.log(b());
// 1

参与互动

44. 看下面代码,给出输出结果(考察时间戳)

参考答案:

//总结:第一个setTimeout,时间间隔<1000的话,输出1000多,>1000的话,输出间隔值多
//     第二个setTimeout,是1000+时间间隔
var dateNum = new Date();
setTimeout(function() {
    console.log(new Date() - dateNum);
}, 1200); //1200多
while (new Date() - dateNum < 1000) {
    var a = 1;
}
setTimeout(function() {
    console.log(new Date() - dateNum);
}, 1500); // 2500左右

参与互动

45. 编写一个元素拖拽的插件

参考答案:

参与互动

46. 什么是代理和通知,写一下他们基本的实现方法

参考答案:

参与互动

47. 看题写结果

var output = function(i) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
};

for (var i = 0; i < 5; i++) {
    output(i); // 这里传过去的 i 值被复制了
}

console.log(i);

参考答案:

5 0 1 2 3 4

解析:参考

参与互动

48. 告诉我参考答案是多少

(function(x) {
    delete x;
    alert(x);
})(1 + 5);

参考答案:

参与互动

49. 写一个通用的事件侦听器函数

参考答案:

// event(事件)工具集,来源:https://github.com/markyun
markyun.Event = {
    // 页面加载完成后
    readyEvent: function(fn) {
        if (fn == null) {
            fn = document;
        }
        var oldonload = window.onload;
        if (typeof window.onload != "function") {
            window.onload = fn;
        } else {
            window.onload = function() {
                oldonload();
                fn();
            };
        }
    },
    // 视能力分别使用dom0||dom2||IE方式 来绑定事件
    // 参数: 操作的元素,事件名称 ,事件处理程序
    addEvent: function(element, type, handler) {
        if (element.addEventListener) {
            //事件类型、需要执行的函数、是否捕捉
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent("on" + type, function() {
                handler.call(element);
            });
        } else {
            element["on" + type] = handler;
        }
    },
    // 移除事件
    removeEvent: function(element, type, handler) {
        if (element.removeEnentListener) {
            element.removeEnentListener(type, handler, false);
        } else if (element.datachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        }
    },
    // 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
    stopPropagation: function(ev) {
        if (ev.stopPropagation) {
            ev.stopPropagation();
        } else {
            ev.cancelBubble = true;
        }
    },
    // 取消事件的默认行为
    preventDefault: function(event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    // 获取事件目标
    getTarget: function(event) {
        return event.target || event.srcElement;
    },
    // 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
    getEvent: function(e) {
        var ev = e || window.event;
        if (!ev) {
            var c = this.getEvent.caller;
            while (c) {
                ev = c.arguments[0];
                if (ev && Event == ev.constructor) {
                    break;
                }
                c = c.caller;
            }
        }
        return ev;
    }
};

参与互动

50. 谈一下 JS 中的递归函数,并且用递归简单实现阶乘

参考答案:递归即是程序在执行过程中不断调用自身的编程技巧,当然也必须要有一个明确的结束条件,不然就会陷入死循环。

参与互动

51. 请用正则表达式写一个简单的邮箱验证

参考答案:

参与互动

52. 完成 foo()函数的内容,要求能够弹出对话框提示当前选中的是第几个单选框

参考答案:

参与互动

53. 完成函数 showImg(),要求能够动态根据下拉列表的选项变化,更新图片的显示

参考答案:

参与互动

54. 截取字符串 abcdefg 中的 efg

参考答案:

参与互动

55. 在 Javascript 中什么是伪数组?如何将伪数组转化为标准数组?

参考答案:

伪数组(类数组):无法直接调用数组方法或期望 length 属性有什么特殊的行为,但仍可以对真正数组遍历方法来遍历它们。典型的是函数的 argument 参数,还有像调用 getElementsByTagName, document.childNodes 之类的, 它们都返回 NodeList 对象都属于伪数组。可以使用 Array.prototype.slice.call(fakeArray)将数组转化为真正的 Array 对象。

假设我们要给每个 log 方法添加一个"(app)"前缀,比如'hello world!' ->'(app)hello world!'。方法如下:

function log() {
    var args = Array.prototype.slice.call(arguments); //为了使用unshift数组方法,将argument转化为真正的数组
    args.unshift("(app)");
    console.log.apply(console, args);
}

参与互动

56. 判断一个字符串中出现次数最多的字符,统计这个次数

参考答案:

var str = "asdfssaaasasasasaa";
var json = {};
for (var i = 0; i < str.length; i++) {
    if (!json[str.charAt(i)]) {
        json[str.charAt(i)] = 1;
    } else {
        json[str.charAt(i)]++;
    }
}
var iMax = 0;
var iIndex = "";
for (var i in json) {
    if (json[i] > iMax) {
        iMax = json[i];
        iIndex = i;
    }
}
alert("出现次数最多的是:" + iIndex + "出现" + iMax + "次");

参与互动

57. 写一个获取非行间样式的函数

参考答案:

function getStyle(obj, attr, value) {
    if (!value) {
        if (obj.currentStyle) {
            return obj.currentStyle(attr);
        } else {
            obj.getComputedStyle(attr, false);
        }
    } else {
        obj.style[attr] = value;
    }
}

参与互动

58. 字符串反转,如将 '12345678' 变成 '87654321'

参考答案:

//思路:先将字符串转换为数组 split(),利用数组的反序函数 reverse()颠倒数组,再利用 jion() 转换为字符串
var str = "12345678";
str = str
    .split("")
    .reverse()
    .join("");

参与互动

59. 将数字 12345678 转化成 RMB 形式 如: 12, 345, 678

参考答案:

//个人方法;
//思路:先将数字转为字符, str= str + '' ;
//利用反转函数,每三位字符加一个 ','最后一位不加; re()是自定义的反转函数,最后再反转回去!
for (var i = 1; i <= re(str).length; i++) {
    tmp += re(str)[i - 1];
    if (i % 3 == 0 && i != re(str).length) {
        tmp += ",";
    }
}

参与互动

60. 生成 5 个不同的随机数

参考答案:

//思路:5个不同的数,每生成一次就和前面的所有数字相比较,如果有相同的,则放弃当前生成的数字!
var num1 = [];
for (var i = 0; i < 5; i++) {
    num1[i] = Math.floor(Math.random() * 10) + 1; //范围是 [1, 10]
    for (var j = 0; j < i; j++) {
        if (num1[i] == num1[j]) {
            i--;
        }
    }
}

参与互动

61. 去掉数组中重复的数字

参考答案:

方法一

//思路:每遍历一次就和之前的所有做比较,不相等则放入新的数组中!
//这里用的原型 个人做法;
Array.prototype.unique = function() {
    var len = this.length,
        newArr = [],
        flag = 1;
    for (var i = 0; i < len; i++, flag = 1) {
        for (var j = 0; j < i; j++) {
            if (this[i] == this[j]) {
                flag = 0; //找到相同的数字后,不执行添加数据
            }
        }
        flag ? newArr.push(this[i]) : "";
    }
    return newArr;
};

方法二

(function(arr) {
    var len = arr.length,
        newArr = [],
        flag;
    for (var i = 0; i < len; i += 1, flag = 1) {
        for (var j = 0; j < i; j++) {
            if (arr[i] == arr[j]) {
                flag = 0;
            }
        }
        flag ? newArr.push(arr[i]) : "";
    }
    alert(newArr);
})([1, 1, 22, 3, 4, 55, 66]);

参与互动

62. 阶乘函数

参考答案:

//原型方法
Number.prototype.N = function() {
    var re = 1;
    for (var i = 1; i <= this; i++) {
        re *= i;
    }
    return re;
};
var num = 5;
alert(num.N());

参与互动

63. 看题做答

参考答案:

function f1() {
    var tmp = 1;
    this.x = 3;
    console.log(tmp); //A
    console.log(this.x); //B
}
var obj = new f1(); //1
console.log(obj.x); //2
console.log(f1()); //3

解析:         这道题让我重新认识了对象和函数,首先看代码(1),这里实例话化了 f1 这个类。相当于执行了 f1 函数。所以这个时候 A 会输出 1, 而 B 这个时候的 this 代表的是 实例化的当前对象 obj B 输出 3.。 代码(2)毋庸置疑会输出 3, 重点 代码(3)首先这里将不再是一个类,它只是一个函数。那么 A 输出 1, B 呢?这里的 this 代表的其实就是 window 对象,那么 this.x 就是一个全局变量 相当于在外部 的一个全局变量。所以 B 输出 3。最后代码由于 f 没有返回值那么一个函数如果没返回值的话,将会返回 underfined ,所以参考答案就是 : 1, 3, 3, 1, 3, underfined 。

参与互动

64. 下面输出多少?

参考答案:

var o1 = new Object();
var o2 = o1;
o2.name = "CSSer";
console.log(o1.name);

解析:

如果不看参考答案,你回答真确了的话,那么说明你对 javascript 的数据类型了解的还是比较清楚了。js 中有两种数据类型,分别是:基本数据类型和引用数据类型(object Array)。对于保存基本类型值的变量,变量是按值访问的,因为我们操作的是变量实际保存的值。对于保存引用类型值的变量,变量是按引用访问的,我们操作的是变量值所引用(指向)的对象。参考答案就清楚了:  CSSer;

参与互动

65. 下面输出多少?

参考答案:

function changeObjectProperty(o) {
    o.siteUrl = "http://www.csser.com/";
    o = new Object();
    o.siteUrl = "http://www.popcg.com/";
}
var CSSer = new Object();
changeObjectProperty(CSSer);
console.log(CSSer.siteUrl); //

解析:

如果 CSSer 参数是按引用传递的,那么结果应该是 "http://www.popcg.com/",但实际结果却仍是"http://www.csser.com/"。事实是这样的:在函数内部修改了引用类型值的参数,该参数值的原始引用保持不变。我们可以把参数想象成局部变量,当参数被重写时,这个变量引用的就是一个局部变量,局部变量的生存期仅限于函数执行的过程中,函数执行完毕,局部变量即被销毁以释放内存。     (补充:内部环境可以通过作用域链访问所有的外部环境中的变量对象,但外部环境无法访问内部环境。每个环境都可以向上搜索作用域链,以查询变量和函数名,反之向下则不能。)

参与互动

66. 输出多少?

参考答案:

var a = 6;
setTimeout(function() {
    var a = 666;
    alert(a); // 输出666,
}, 1000);
a = 66;

因为 var a = 666; 定义了局部变量 a,并且赋值为 666,根据变量作用域链, 全局变量处在作用域末端,优先访问了局部变量,从而覆盖了全局变量 。

var a = 6;
setTimeout(function() {
    alert(a); // 输出undefined
    var a = 666;
}, 1000);
a = 66;

因为 var a = 666; 定义了局部变量 a,同样覆盖了全局变量,但是在 alert(a); 之前 a 并未赋值,所以输出 undefined。

var a = 6;
setTimeout(function() {
    alert(a);
    var a = 66;
}, 1000);
a = 666;
alert(a);
// 666, undefined;

记住: 异步处理,一切 OK 声明提前

参与互动

67. JS 的继承性?

参考答案:

window.color = "red";
var o = {
    color: "blue"
};

function sayColor() {
    alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red this-window对象
sayColor.call(window); //red
sayColor.call(o); //blue

参与互动

68. 精度问题: JS 精度不能精确到 0.1 所以  。。。。同时存在于值和差值中

参考答案:

var n = 0.3,
    m = 0.2,
    i = 0.2,
    j = 0.1;
alert(n - m == i - j); //false
alert(n - m == 0.1); //false
alert(i - j == 0.1); //true

参与互动

69. 加减运算

参考答案:

alert("5" + 3); //53 string
alert("5" + "3"); //53 string
alert("5" - 3); //2 number
alert("5" - "3"); //2 number

参与互动

70. 结果是什么?

参考答案:

function foo() {
    foo.a = function() {
        alert(1);
    };
    this.a = function() {
        alert(2);
    };
    a = function() {
        alert(3);
    };
    var a = function() {
        alert(4);
    };
}
foo.prototype.a = function() {
    alert(5);
};
foo.a = function() {
    alert(6);
};
foo.a(); //6
var obj = new foo();
obj.a(); //2
foo.a(); //1

参与互动

71. 输出结果

参考答案:

var a = 5;

function test() {
    a = 0;
    alert(a);
    alert(this.a); //没有定义 a这个属性
    var a;
    alert(a);
}
test(); // 0, 5, 0
new test(); // 0, undefined, 0 //由于类它自身没有属性a, 所以是undefined

参与互动

72. 计算字符串字节数

参考答案:

new(function(s) {
    if (!arguments.length || !s) return null;
    if ("" == s) return 0;
    var l = 0;
    for (var i = 0; i < s.length; i++) {
        if (s.charCodeAt(i) > 255) l += 2;
        else l += 1; //charCodeAt()得到的是unCode码
    } //汉字的unCode码大于 255bit 就是两个字节
    alert(l);
})("hello world!");

参与互动

73. 输出结果

参考答案:

var bool = !!2;  alert(bool); //true; 双向非操作可以把字符串和数字转换为布尔值

参与互动

74. 声明对象,添加属性,输出属性

参考答案:

var obj = {
    name: "leipeng",
    showName: function() {
        alert(this.name);
    }
};
obj.showName();

参与互动

75. 匹配输入的字符:第一个必须是字母或下划线开头,长度 5-20

参考答案:

var reg = /^[a-zA-Z][a-zA-Z0-9_]{5,20}/,
    name1 = "leipeng",
    name2 = "0leipeng",
    name3 = "你好leipeng",
    name4 = "hi";
alert(reg.test(name1));
alert(reg.test(name2));
alert(reg.test(name3));
alert(reg.test(name4));

参与互动

76. 检测变量类型

参考答案:

function checkStr(str) {
    typeof str == "string" ? alert("true") : alert("false");
}
checkStr("leipeng");

参与互动

77. 如何在 HTML 中添加事件,几种方法?

参考答案:

1、标签之中直接添加 onclick="fun()";
2、JS 添加 Eobj.onclick = method;
3、现代事件  IE: obj.attachEvent('onclick', method);
            FF: obj.addEventListener('click', method, false);

参与互动

78. 请问代码实现 outerHTML

参考答案:

//说明:outerHTML其实就是innerHTML再加上本身;
Object.prototype.outerHTML = function() {
    var innerCon = this.innerHTML, //获得里面的内容
        outerCon = this.appendChild(innerCon); //添加到里面
    alert(outerCon);
};

演示代码:

<! DOCTYPE html>
<html>
  <head>

    <meta charset="UTF-8" />
    <title>Document</title>

  </head>
  <body>

    <div id="outer">
      hello
    </div>
    <script>
      Object.prototype.outerHTML = function() {
        var innerCon = this.innerHTML, //获得里面的内容
          outerCon = this.appendChild(innerCon); //添加到里面
        alert(outerCon);
      };
      function $(id) {
        return document.getElementById(id);
      }
      alert($("outer").innerHTML);
      alert($("outer").outerHTML);
    </script>

  </body>
</html>

参与互动

79.JS 中的简单继承 call 方法

参考答案:

//顶一个父母类,注意:类名都是首字母大写的哦!
function Parent(name, money) {
  this.name = name;
  this.money = money;
  this.info = function() {
    alert("姓名: " + this.name + " 钱: " + this.money);
  };
} //定义孩子类
function Children(name) {
  Parent.call(this, name); //继承 姓名属性,不要钱。
  this.info = function() {
    alert("姓名: " + this.name);
  };
} //实例化类
var per = new Parent("parent", 800000000000);
var chi = new Children("child");
per.info();
chi.info();

参与互动

80. 解析 URL 成一个对象?

参考答案:

String.prototype.urlQueryString = function() {
    var url = this.split("?")[1].split("&"),
        len = url.length;
    this.url = {};
    for (var i = 0; i < len; i += 1) {
        var cell = url[i].split("="),
            key = cell[0],
            val = cell[1];
        this.url["" + key + ""] = val;
    }
    return this.url;
};
var url = "?name=12&age=23";
console.log(url.urlQueryString().age);

参与互动

81. 看下列代码输出什么?

参考答案:

var foo = "11" + 2 - "1";
console.log(foo);
console.log(typeof foo);
// 执行完后foo的值为111,foo的类型为Number。

参与互动

82. 看下列代码, 输出什么?

参考答案:

var a = new Object();
a.value = 1;
b = a;
b.value = 2;
alert(a.value);
// 执行完后输出结果为2

参与互动

83. 已知数组 var stringArray = ["This", "is", "Baidu", "Campus"],Alert 出"This is Baidu Campus"。

参考答案:alert(stringArray.join(""))

参与互动

84. 请描述出下列代码运行的结果

参考答案:

function d() {
    console.log(this);
}
d();

参与互动

85. 需要将变量 e 的值修改为"a+b+c+d", 请写出对应的代码

var e="abcd";

参考答案:

e.split('').join('+')

参与互动

86. 设计一段代码能够遍历下列整个 DOM 节点

<div>
    <p>
        <span><a></a></span>
        <span><a></a></span>
    </p>
    <ul>
        <li></li>
        <li></li>
    </ul>
</div>

参考答案:

参与互动

87. 怎样实现两栏等高?

参考答案:

参与互动

88. 使用 js 实现这样的效果:在文本域里输入文字时,当按下 enter 键时不换行,而是替换成"{{enter}}", (只需要考虑在行尾按下 enter 键的情况).

参考答案:

参与互动

89. 以下代码中 end 字符串什么时候输出

var t = true;
setTimeout(function() {
    console.log(123);
    t = false;
}, 1000);
while (t) {}
console.log("end");

参考答案:

参与互动

90. specify('hello, world')//=>'h, e, l, l, o, w, o, r, l, d'实现 specify 函数

参考答案:

参与互动

91. 请将一个 URL 的 search 部分参数与值转换成一个 json 对象

参考答案:

参与互动

92. 请用原生 js 实现 jquery 的 get\post 功能,以及跨域情况下

参考答案:

参与互动

93. 请写出三种以上的 Firefox 有但 IE 没有的属性和函数

参考答案:

1、在 IE 下可通过 document.frames["id"]; 得到该 IFRAME 对象,

而在火狐下则是通过 document.getElementById("content_panel_if").contentWindow;

2、IE 的写法: _tbody=_table.childNodes[0] 在 FF 中,firefox 会在子节点中包含空白则第一个子节点为空白"", 而 ie 不会返回空白 可以通过 if("" != node.nodeName) 过滤掉空白子对象

3、模拟点击事件

if (document.all) {
    //ie下
    document.getElementById("a3").click();
} else {
    //非IE
    var evt = document.createEvent("MouseEvents");
    evt.initEvent("click", true, true);
    document.getElementById("a3").dispatchEvent(evt);
}

4、事件注册

if (isIE) {
    window.attachEvent("onload", init);
} else {
    window.addEventListener("load", init, false);
}

参与互动

94. 写出 3 个使用 this 的典型应用

参考答案:

(1)、在 html 元素事件属性中使用,如:

<input type="button" οnclick="showInfo(this);" value="点击一下" />

(2)、构造函数

function Animal(name, color) {
    this.name = name;
    this.color = color;
}

(3)、input 点击,获取值

< input type = "button"
id = "text"
value = "点击一下" / >
    <
    script type = "text/javascript" >
    var btn = document.getElementById("text");
btn.onclick = function() {
        alert(this.value); //此处的this是按钮元素
    } <
    /script>

(4)、apply()/call()求数组最值

var numbers = [5, 458, 120, -215];
var maxInNumbers = Math.max.apply(this, numbers);
console.log(maxInNumbers); // 458
var maxInNumbers = Math.max.call(this, 5, 458, 120, -215);
console.log(maxInNumbers); // 458

参与互动

95. 下面这个 ul,如何点击每一列的时候 alert 其 index?(闭包)

<ul id="test">
    <li>这是第一条</li>
    <li>这是第二条</li>
    <li>这是第三条</li>
</ul>

参考答案:

// 方法一:
var lis = document.getElementById("test").getElementsByTagName("li");
for (var i = 0; i < 3; i++) {
    lis[i].index = i;
    lis[i].onclick = function() {
        alert(this.index);
    };
}
//方法二:
var lis = document.getElementById("test").getElementsByTagName("li");
for (var i = 0; i < 3; i++) {
    lis[i].index = i;
    lis[i].onclick = (function(a) {
        return function() {
            alert(a);
        };
    })(i);
}

参与互动

96. 小贤是一条可爱的小狗(Dog),它的叫声很好听(wow),每次看到主人的时候就会乖乖叫一声(yelp)。从这段描述可以得到以下对象:

参考答案:

function Dog() {
    this.wow = function() {
        alert("Wow");
    };
    this.yelp = function() {
        this.wow();
    };
}

小芒和小贤一样,原来也是一条可爱的小狗,可是突然有一天疯了(MadDog),一看到人就会每隔半秒叫一声(wow)地不停叫唤(yelp)。请根据描述,按示例的形式用代码来实。(继承,原型,setInterval)

function MadDog() {
    this.yelp = function() {
        var self = this;
        setInterval(function() {
            self.wow();
        }, 500);
    };
}
MadDog.prototype = new Dog();
//for test
var dog = new Dog();
dog.yelp();
var madDog = new MadDog();
madDog.yelp();

参与互动

97. 实现一个函数 clone,可以对 JavaScript 中的 5 种主要的数据类型(包括 Numer、String、Object、Array、Boolean)进行值复制

参考答案:

  • 察点 1:对于基本数据类型和引用数据类型在内存中存放的是值还是指针这一区别是否清楚
  • 察点 2:是否知道如何判断一个变量是什么类型的
  • 考察点 3:递归算法的设计
// 方法一:
Object.prototype.clone = function() {
    var o = this.constructor === Array ? [] : {};
    for (var e in this) {
        o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
    }
    return o;
};
/**
 * 克隆一个对象
 * @param Obj
 * @returns
 */
//方法二:
function clone(Obj) {
    var buf;
    if (Obj instanceof Array) {
        buf = []; //创建一个空的数组
        var i = Obj.length;
        while (i--) {
            buf[i] = clone(Obj[i]);
        }
        return buf;
    } else if (Obj instanceof Object) {
        buf = {}; //创建一个空对象
        for (var k in Obj) {
            //为这个对象添加新的属性
            buf[k] = clone(Obj[k]);
        }
        return buf;
    } else {
        //普通变量直接赋值
        return Obj;
    }
}

参与互动

98. 用 js 实现随机选取 10–100 之间的 10 个数字,存入一个数组,并排序。

参考答案:

var iArray = [];
funtion getRandom(istart, iend) {
        var iChoice = istart - iend + 1;
        return Math.floor(Math.random() * iChoice + istart;
        }
        for (var i = 0; i < 10; i++) {
            iArray.push(getRandom(10, 100));
        }
        iArray.sort();

参与互动

99. 输出今天的日期,以 YYYY-MM-DD 的方式,比如今天是 2014 年 9 月 26 日,则输出 2014-09-26

参考答案:

var d = new Date();
// 获取年,getFullYear()返回4位的数字
var year = d.getFullYear();
// 获取月,月份比较特殊,0是1月,11是12月
var month = d.getMonth() + 1;
// 变成两位
month = month < 10 ? "0" + month : month;
// 获取日
var day = d.getDate();
day = day < 10 ? "0" + day : day;
alert(year + "-" + month + "-" + day);

参与互动

100. 写出函数 DateDemo 的返回结果,系统时间假定为今天

function DateDemo() {
    var d,
        s = "今天日期是:";
    d = new Date();
    s += d.getMonth() + "/";
    s += d.getDate() + "/";
    s += d.getYear();
    return s;
}

参考答案:今天日期是:7/17/2019

参与互动

101. 下列 JavaScript 代码执行后,依次 alert 的结果是

var obj = {
    proto: {
        a: 1,
        b: 2
    }
};

function F() {}
F.prototype = obj.proto;
var f = new F();
obj.proto.c = 3;
obj.proto = {
    a: -1,
    b: -2
};
alert(f.a);
alert(f.c);
delete F.prototype["a"];
alert(f.a);
alert(obj.proto.a);

参考答案:

参与互动

102. 下列 JavaScript 代码执行后,运行的结果是

<button id="btn">点击我</button>
var btn = document.getElementById("btn");
var handler = {
    id: "_eventHandler",
    exec: function() {
        alert(this.id);
    }
};
btn.addEventListener("click", handler.exec.false);

参考答案:

参与互动

103. 输出结果是多少?

1)

var a; // undefined
var b = a * 0; // NaN
if (b == b) {
    // false
    console.log(b * 2 + "2" - 0 + 4);
} else {
    console.log(!b * 2 + "2" - 0 + 4); // 22 + 4 = 26
}

参考答案:26

2)

< script >
    var a = 1; <
/script> <
script >
    var a;
var b = a * 0;
if (b == b) { // true
    console.log(b * 2 + "2" - 0 + 4); // 6
} else {
    console.log(!b * 2 + "2" - 0 + 4);
} <
/script>

参考答案:6

3)

var t = 10;

function test(t) {
    var t = t++;
}
test(t);
console.log(t); // 外部不能访问函数内的变量

参考答案:10

4)

var t = 10;

function test(test) {
    var t = test++;
}
test(t);
console.log(t);

参考答案:10

6)

var t = 10;

function test(test) {
    t = test++;
    console.log(t);
}
test(t);
console.log(t);

参考答案:10

7)

var t = 10;

function test(test) {
    t = t + test;
    console.log(t);
    var t = 3;
}
test(t);
console.log(t);

参考答案:NaN 10

8)

var a;
var b = a / 0;
if (b == b) {
    console.log(b * 2 + "2" - 0 + 4);
} else {
    console.log(!b * 2 + "2" - 0 + 4);
}

参考答案:26

9)

< script >
    var a = 1; <
/script> <
script >
    var a;
var b = a / 0;
if (b == b) {
    console.log(b * 2 + "2" + 4);
} else {
    console.log(!b * 2 + "2" + 4);
} <
/script>

参考答案:Infinity24

参与互动

104. 下列 JavaScript 代码执行后,iNum 的值是

var iNum = 0;
for (var i = 1; i < 10; i++) {
    if (i % 5 == 0) {
        continue;
    }
    iNum++;
}
console.log(iNum);

参考答案:8

参与互动

105. 下列 JavaScript 代码执行后,依次打印的结果是

(function test() {
    var a = (b = 5);
    console.log(typeof a);
    console.log(typeof b);
})();
console.log(typeof a);
console.log(typeof b);

参考答案:

number
number
undefined
number

参与互动

106. 不用任何插件,如何实现一个 tab 栏切换?

参考答案:

参与互动

107. js 中如何实现一个 map

参考答案:

数组的map方法:

概述
map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

语法
array.map(callback[, thisArg])

参数
callback

原数组中的元素经过该方法后返回一个新的元素。

currentValue

callback 的第一个参数,数组中当前被传递的元素。

index

callback 的第二个参数,数组中当前被传递的元素的索引。

array

callback 的第三个参数,调用 map 方法的数组。

thisArg

执行 callback 函数时 this 指向的对象。

实现:

Array.prototype.map2 = function(callback) {
    for (var i = 0; i < this.length; i++) {
        this[i] = callback(this[i]);
    }
};

参与互动

108. 有 1 到 10w 这个 10w 个数,去除 2 个并打乱次序,如何找出那两个数?

参考答案:

参与互动

109. 如何获取对象 a 拥有的所有属性(可枚举的、不可枚举的,不包括继承来的属性)

参考答案:

Object.keys—— IE9 +

或者使用 for…in 并过滤出继承的属性

for (o in obj) {
    if (obj.hasOwnproperty(o)) {
        //把o这个属性放入到一个数组中
    }
}

参与互动

110. 约瑟夫环—已知 n 个人(以编号 1,2,3…分别表示)围坐在一张圆桌周围。从编号为 k 的人开始报数,数到 m 的那个人出列;他的下一个人又从 1 开始报数,数到 m 的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

参考答案:

参与互动

111. FF 与 IE 中如何阻止事件冒泡,如何获取事件对象,以及如何获取触发事件的元素

参考答案:

参与互动

112. 下列控制台都输出什么

参考答案:

第 1 题:

function setName() {
    name = "张三";
}
setName();
console.log(name);

参考答案:"张三"

第 2 题:

//考点:1、变量声明提升 2、变量搜索机制
var a = 1;

function test() {
    console.log(a);
    var a = 1;
}
test();

参考答案:undefined

第 3 题:

var b = 2;

function test2() {
    window.b = 3;
    console.log(b);
}
test2();

参考答案:3

第 4 题:

c = 5; //声明一个全局变量c
function test3() {
    window.c = 3;
    console.log(c); //参考答案:undefined,原因:由于此时的c是一个局部变量c,并且没有被赋值
    var c;
    console.log(window.c); //参考答案:3,原因:这里的c就是一个全局变量c
}
test3();

第 5 题:

var arr = [];
arr[0] = "a";
arr[1] = "b";
arr[10] = "c";
alert(arr.length); //参考答案:11
console.log(arr[5]); //参考答案:undefined

第 6 题:

var a = 1;
console.log(a++); //参考答案:1
console.log(++a); //参考答案:3

第 7 题:

console.log(null == undefined); //参考答案:true
console.log("1" == 1); //参考答案:true,因为会将数字1先转换为字符串1
console.log("1" === 1); //参考答案:false,因为数据类型不一致

第 8 题:

typeof 1;
("number");
typeof "hello";
("string");
typeof /[0-9]/;
("object");
typeof {};
("object");
typeof null;
("object");
typeof undefined;
("undefined");
typeof [1, 2, 3];
("object");
typeof

function() {}; //"function"

第 9 题:

parseInt(3.14); //3
parseFloat("3asdf"); //3
parseInt("1.23abc456");
parseInt(true); //"true" NaN

第 10 题:

//考点:函数声明提前
function bar() {
    return foo;
    foo = 10;

    function foo() {}
    //var foo = 11;
}
alert(typeof bar()); //"function"

第 11 题:考点:函数声明提前

var foo = 1;

function bar() {
    foo = 10;
    return;

    function foo() {}
}
bar();
alert(foo); //参考答案:1

第 12 题:

console.log(a); //是一个函数
var a = 3;

function a() {}
console.log(a); ////3

第 13 题:

//考点:对arguments的操作
function foo(a) {
    arguments[0] = 2;
    alert(a); //参考答案:2,因为:a、arguments是对实参的访问,b、通过arguments[i]可以修改指定实参的值
}
foo(1);
265 第14题:
function foo(a) {
    alert(arguments.length); //参考答案:3,因为arguments是对实参的访问
}
foo(1, 2, 3);

第 15 题

bar(); //报错
var foo = function bar(name) {
    console.log("hello" + name);
    console.log(bar);
};
//alert(typeof bar);
foo("world"); //"hello"
console.log(bar); //undefined
console.log(foo.toString());
bar(); //报错

第 16 题

function test() {
    console.log("test函数");
}
setTimeout(function() {
    console.log("定时器回调函数");
}, 0);
test();

function foo() {
    var name = "hello";
}

参与互动

113. console.log( 8 | 1 ); 输出值是多少?

参考答案:9

参与互动

114. 只允许使用 + - _ / 和 Math._ ,求一个函数 y = f(x, a, b); 当 x > 100 时返回 a 的值,否则返回 b 的值,不能使用 if else 等条件语句,也不能使用|, ?:, 数组。

参考答案:

function f(x, a, b) {
    var temp = Math.ceil(Math.min(Math.max(x - 100, 0), 1));
    return a * temp + b * (1 - temp);
}
console.log(f(-10, 1, 2));

参与互动

115. JavaScript alert(0.4*0.2); 结果是多少?和你预期的一样吗?如果不一样该如何处理?

参考答案:有误差,应该比准确结果偏大。 一般我会将小数变为整数来处理。当前之前遇到这个问题时也上网查询发现有人用 try catch return 写了一个函数, 当然原理也是一致先转为整数再计算。看起来挺麻烦的,我没用过。

参与互动

116. 如何显示/隐藏一个 dom 元素?请用原生的 JavaScript 方法实现

参考答案:

dom.style.display="none"; dom.style.display="block";

参与互动

117. 编写一个 JavaScript 函数,输入指定类型的选择器(仅需支持 id,class,tagName 三种简单 CSS 选择器,无需兼容组合选择器)可以返回匹配的 DOM 节点,需考虑浏览器兼容性和性能。

参考答案:

/*** @param selector {String} 传入的CSS选择器。* @return {Array}*/
var query = function(selector) {
    var reg = /^(#)?(\.)?(\w+)$/gim;
    var regResult = reg.exec(selector);
    var result = [];
    //如果是id选择器
    if (regResult[1]) {
        if (regResult[3]) {
            if (typeof document.querySelector === "function") {
                result.push(document.querySelector(regResult[3]));
            } else {
                result.push(document.getElementById(regResult[3]));
            }
        }
    } //如果是class选择器
    else if (regResult[2]) {
        if (regResult[3]) {
            if (typeof document.getElementsByClassName === "function") {
                var doms = document.getElementsByClassName(regResult[3]);
                if (doms) {
                    result = converToArray(doms);
                }
            } //如果不支持getElementsByClassName函数
            else {
                var allDoms = document.getElementsByTagName("*");
                for (var i = 0, len = allDoms.length; i < len; i++) {
                    if (allDoms[i].className.search(new RegExp(regResult[2])) > -1) {
                        result.push(allDoms[i]);
                    }
                }
            }
        }
    } //如果是标签选择器
    else if (regResult[3]) {
        var doms = document.getElementsByTagName(regResult[3].toLowerCase());
        if (doms) {
            result = converToArray(doms);
        }
    }
    return result;
};

function converToArray(nodes) {
    var array = null;
    try {
        array = Array.prototype.slice.call(nodes, 0); //针对非IE浏览器
    } catch (ex) {
        array = new Array();
        for (var i = 0, len = nodes.length; i < len; i++) {
            array.push(nodes[i]);
        }
    }
    return array;
}

参与互动

118. 如现在有一个效果,有显示用户头像、用户昵称、用户其他信息;当用户鼠标移到头像上时,会弹出用户的所有信息;如果是你,你会如何实现这个功能,请用代码实现?

参考答案:

参与互动

119. call 与 apply 有什么作用?又有什么什么区别?用 callee 属性实现函数递归?

参考答案:apply 的参数是数组, call 的参数是单个的值,除此之外,两者没有差别,重点理解 this 的改变,callee 已经不推荐使用

参与互动

120. 用正则表达式,写出由字母开头,其余由数字、字母、下划线组成的 6~30 的字符串?

参考答案:var reg=/^[a-ZA-Z][\da-za-z_]{5, 29}/;

参与互动

121. 写一个函数可以计算 sum(5, 0, -5); 输出 0; sum(1, 2, 3, 4); 输出 10;

参考答案:

function calc() {
    var result = 0;
    for (var i = 0; i < arguments.length; i++) {
        var obj = arguments[i];
        result += obj;
    }
    return result;
}
alert(calc(1, 2, 3, 4));

参与互动

122. 请评价以下代码并给出改进意见

参考答案:

if (window.addEventListener) {
    var addListener = function(el, type, listener, useCapture) {
        el.addEventListener(type, listener, useCapture);
    };
} else if (document.all) {
    addListener = function(el, type, listener) {
        el.attachEvent("on" + type, function() {
            listener.apply(el);
        });
    };
}

* 不应该在 if 和 else 语句中声明 addListener 函数,应该先声明; * 不需要使用 window.addEventListener 或 document.all 来进行检测浏览器,应该使用能力检测; * 由于 attachEvent 在 IE 中有 this 指向问题,所以调用它时需要处理一下

改进如下:

function addEvent(elem, type, handler) {
    if (elem.addEventListener) {
        elem.addEventListener(type, handler, false);
    } else if (elem.attachEvent) {
        elem["temp" + type + handler] = handler;
        elem[type + handler] = function() {
            elem["temp" + type + handler].apply(elem);
        };
        elem.attachEvent("on" + type, elem[type + handler]);
    } else {
        elem["on" + type] = handler;
    }
}

参与互动

123. 对于 apply 和 call 两者在作用上是相同的,即是调用一个对象的一个方法,以另一个对象替换当前对象。将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

参考答案:但两者在参数上有区别的。对于第一个参数意义都一样,但对第二个参数:?apply 传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而 call 则作为 call 的参数传入(从第二个参数开始)。? 如 func.call(func1, var1, var2, var3)对应的 apply 写法为:func.apply(func1, [var1, var2, var3]) 。

参与互动

124. 《正则》写出正确的正则表达式匹配固话号,区号 3-4 位,第一位为 0,中横线,7-8 位数字,中横线,3-4 位分机号格式的固话号

参考答案:/0[0-9]{2, 3}-\d{7, 8}/

参与互动

125. 《算法》 一下 A, B 可任选一题作答,两题全答加分

A: 农场买了一只羊,第一年是小羊,第二年底生一只,第三年不生,第四年底再生一只,第五年死掉。

B: 写出代码对下列数组去重并从大到小排列{5, 2, 3, 6, 8, 6, 5, 4, 7, 1, 9}

参考答案:

参与互动

126. 给 String 对象添加一个方法,传入一个 string 类型的参数,然后将 string 的每个字符间加个空格返回,例如:addSpace("hello world") // -> 'h e l l o  w o r l d'

参考答案:

String.prototype.spacify = function() {
    return this.split("").join(" ");
};

接着上述问题参考答案提问,1)直接在对象的原型上添加方法是否安全?尤其是在 Object 对象上。(这个我没能答出?希望知道的说一下。)  2)函数声明与函数表达式的区别?

参考答案:在 js 中,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非是一视同仁的,解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问),至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析执行。

参与互动

127. 请写一个正则表达式:要求最短 6 位数,最长 20 位,阿拉伯数和英文字母(不区分大小写)组成

参考答案:^(?=.\d)(?=.[a-z])(?=.*[A-Z])[a-zA-Z\d]{6, 20}$

参与互动

128. 统计 1 到 400 亿之间的自然数中含有多少个 1?比如 1-21 中,有 1、10、11、21 这四个自然数有 13 个 1

参考答案:

归纳法

归纳法,对于个位出现的1:(n / 10) * 1 + (n % 10) >= 1 ? 1 : 0; 对于十位出现的1:(n / 100) * 10 + if (k > 19) 10 else if (k < 10) 0 else k - 10 + 1; 对于百位出现的1:(n / 1000) * 100 + if (k > 199) 10 else if (k < 100) 0 else k - 100 + 1; 最终归纳出: (n / (i * 10)) * i + if (k > 2 * i - 1) i else if (k < i) 0 else k - i + 1, 其中k = n % (i * 10);

代码:

var countDigitOne = function(n) {
    let count = 0;

    for (let i = 1; i <= n; i *= 10) {
        let divide = i * 10;
        let p = Math.floor(n / divide), k = n % divide, rest = 0;

        count += p * i;
        rest = (k > (2 * i - 1)) ? i : ((k < i) ? 0 : k - i + 1);
        count += rest;
    }
    return count;
};
countDigitOne(40000000000)

参与互动

129. 删除与某个字符相邻且相同的字符,比如 fdaffdaaklfjklja 字符串处理之后成为"fdafdaklfjklja"

参考答案:

参与互动

130. 请写出一个程序,在页面加载完成后动态创建一个 form 表单,并在里面添加一个 input 对象并给它任意赋值后义 post 方式提交到:http://127.0.0.1/save.php

参考答案:

参与互动

131. 定义一个 log 方法,让它可以代理 console.log 的方法。

参考答案:

可行的方法一:

function log(msg) {
    console.log(msg);
}
log("hello world!"); // hello world!

如果要传入多个参数呢?显然上面的方法不能满足要求,所以更好的方法是:

function log() {
    console.log.apply(console, arguments);
}

到此,追问 apply 和 call 方法的异同。

对于 apply 和 call 两者在作用上是相同的,即是调用一个对象的一个方法,以另一个对象替换当前对象。将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

但两者在参数上有区别的。对于第一个参数意义都一样,但对第二个参数: apply 传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而 call 则作为 call 的参数传入(从第二个参数开始)。  如 func.call(func1, var1, var2, var3)对应的 apply 写法为:func.apply(func1, [var1, var2, var3]) 。

参与互动

132. 编写一个快速方法将 html 的 sup 提取转换为一个数组

// 编写一个快速方法将html的sup提取转换为一个数组,如:
let str = "气量(10<sup>8</sup>m<sup>3</sup>)";
// 输出结果
// ['气量(10',8,'m',3,')']

参考答案:

// 方法1
str.split(/\<\/?sup\>/);
// 方法2
str.split(/<[^>]+>/);

参与互动

133. 求 num 的值

参考答案:

// 面试题1
var num = 123;

function f1() {
    console.log(num); // 123
}

function f2() {
    var num = 456;
    f1();
}
f2();

// 面试题1 变式
var num = 123;

function f1(num) {
    console.log(num); // 456
}

function f2() {
    var num = 456;
    f1(num);
}
f2();

// 面试题1 变式
var num = 123;

function f1() {
    console.log(num); // 456
}
f2();

function f2() {
    num = 456; //这里是全局变量
    f1();
}
console.log(num); // 456

参与互动

134. 有一个函数,参数是一个函数,返回值也是一个函数,返回的函数功能和入参的函数相似,但这个函数只能执行 3 次,再次执行无效,如何实现

这个题目是考察闭包的使用

参考答案:

function sayHi() {
    console.log("hi");
}

function threeTimes(fn) {
    let times = 0;
    return () => {
        if (times++ < 3) {
            fn();
        }
    };
}

const newFn = threeTimes(sayHi);
newFn();
newFn();
newFn();
newFn();
newFn(); // 后面两次执行都无任何反应

通过闭包变量 times 来控制函数的执行

参与互动

135. 实现 add 函数, 让 add(a)(b)和 add(a, b)两种调用结果相同

参考答案:

function add(a, b) {
    if (b === undefined) {
        return function(x) {
            return a + x;
        };
    }
    return a + b;
}

参与互动

136. 格式化金钱,每千分位加逗号

参考答案:

function format(str) {
    let s = "";
    let count = 0;
    for (let i = str.length - 1; i >= 0; i--) {
        s = str[i] + s;
        count++;
        if (count % 3 == 0 && i != 0) {
            s = "," + s;
        }
    }
    return s;
}
function format(str) {
    return str.replace(/(\d)(?=(?:\d{3})+$)/g, "$1,");
}

参与互动

137. 反转数组

要求

input: I am a student
output: student a am I
输入是数组 输出也是数组
不允许用 split splice reverse

参考答案:

解法一

function reverseArry(arry) {
    const str = arry.join(" ");
    const result = [];
    let word = "";
    for (let i = 0, len = str.length; i < len; i++) {
        if (str[i] != " ") {
            word += str[i];
        } else {
            result.unshift(word);
            word = "";
        }
    }

    result.unshift(word);
    return result;
}

console.log(reverseArry(["I", "am", "a", "student"]));
// ["student", "a", "am", "I"]

解法二

function reverseArry(arry) {
    const result = [];
    const distance = arry.length - 1;
    for (let i = distance; i >= 0; i--) {
        result[distance - i] = arry[i];
    }

    return result;
}

参与互动

138. 说出以下函数的作用是?空白区域应该填写什么?

参考答案:

//define
(function(window) {
    function fn(str) {
        this.str = str;
    }
    fn.prototype.format = function() {
        var arg = ______;
        return this.str.replace(_____, function(a, b) {
            return arg[b] || "";
        });
    };
    window.fn = fn;
})(window);
//use
(function() {
    var t = new fn('<p><a href="{0}">{1}</a><span>{2}</span></p>');
    console.log(t.format("http://www.alibaba.com", "Alibaba", "Welcome"));
})();

参考答案:访函数的作用是使用 format 函数将函数的参数替换掉{0}这样的内容,返回一个格式化后的结果: 第一个空是:arguments 第二个空是:/{(\d+)}/ig

参与互动

139. (设计题)想实现一个对页面某个节点的拖曳?如何做?(使用原生 JS)

参考答案:

回答出概念即可,下面是几个要点

  1. 给需要拖拽的节点绑定 mousedown, mousemove, mouseup 事件
  2. mousedown 事件触发后,开始拖拽
  3. mousemove 时,需要通过 event.clientX 和 clientY 获取拖拽位置,并实时更新位置
  4. mouseup 时,拖拽结束
  5. 需要注意浏览器边界的情况

参与互动

140. 原生 JS 的 window.onload 与 Jquery 的$(document).ready(function(){})有什么不同?如何用原生 JS 实现 Jq 的 ready 方法?

参考答案:

window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行。 $(document).ready()是 DOM 结构绘制完毕后就执行,不必等到加载完毕。

/*
 * 传递函数给whenReady()
 * 当文档解析完毕且为操作准备就绪时,函数作为document的方法调用
 */
var whenReady = (function() {
    //这个函数返回whenReady()函数
    var funcs = []; //当获得事件时,要运行的函数
    var ready = false; //当触发事件处理程序时,切换为true //当文档就绪时,调用事件处理程序
    function handler(e) {
        if (ready) return; //确保事件处理程序只完整运行一次 //如果发生onreadystatechange事件,但其状态不是complete的话,那么文档尚未准备好
        if (e.type === "onreadystatechange" && document.readyState !== "complete") {
            return;
        } //运行所有注册函数 //注意每次都要计算funcs.length //以防这些函数的调用可能会导致注册更多的函数
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].call(document);
        } //事件处理函数完整执行,切换ready状态, 并移除所有函数
        ready = true;
        funcs = null;
    } //为接收到的任何事件注册处理程序
    if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", handler, false);
        document.addEventListener("readystatechange", handler, false); //IE9+
        window.addEventListener("load", handler, false);
    } else if (document.attachEvent) {
        document.attachEvent("onreadystatechange", handler);
        window.attachEvent("onload", handler);
    } //返回whenReady()函数
    return function whenReady(fn) {
        if (ready) {
            fn.call(document);
        } else {
            funcs.push(fn);
        }
    };
})();

如果上述代码十分难懂,下面这个简化版:

function ready(fn) {
    if (document.addEventListener) {
        //标准浏览器
        document.addEventListener(
            "DOMContentLoaded",
            function() {
                //注销事件, 避免反复触发
                document.removeEventListener(
                    "DOMContentLoaded",
                    arguments.callee,
                    false
                );
                fn(); //执行函数
            },
            false
        );
    } else if (document.attachEvent) {
        //IE
        document.attachEvent("onreadystatechange", function() {
            if (document.readyState == "complete") {
                document.detachEvent("onreadystatechange", arguments.callee);
                fn(); //函数执行
            }
        });
    }
}

参与互动

141. 对作用域上下文和 this 的理解,看下列代码:

var User = {
    count: 1,
    getCount: function() {
        return this.count;
    }
};
console.log(User.getCount()); // what?
var func = User.getCount;
console.log(func()); // what?

问两处 console 输出什么?为什么?

参考答案:1 和 undefined。func 是在 winodw 的上下文中被执行的,所以会访问不到 count 属性。

解析:

继续追问,那么如何确保 Uesr 总是能访问到 func 的上下文,即正确返回 1。正确的方法是使用 Function.prototype.bind。兼容各个浏览器完整代码如下:

Function.prototype.bind =
    Function.prototype.bind ||
    function(context) {
        var self = this;
        return function() {
            return self.apply(context, arguments);
        };
    };
var func = User.getCount.bind(User);
console.log(func());

参与互动

142. 手动封装一个请求函数,可以设置最大请求次数,请求成功则不再请求,请求失败则继续请求直到超过最大次数(流利说)

参考答案:

143. 实现一个函数判断数据类型(哔哩哔哩)

参考答案:

144. 对象数组如何去重?(烈熊网络)

参考答案:

145. 实现数组的filter方法

参考答案:

146. 实现数组的flat方法

参考答案:

147. 如何实现一个模板引擎

参考答案:

148. 实现一个最基本的数据绑定

参考答案:

149. 请实现一个函数,找出这个树形结构中所有child的name,返回数组

参考答案:

150. 解析 URL Params 为对象

参考答案:

151. 统计 1 ~ n 整数中出现 1 的次数。

参考答案:

152. 如何将 [{id: 1}, {id: 2, pId: 1}, ... ] 的重复数组(有重复数据)转成树形结构的数组 [{id: 1, child: [{id: 2, pId: 1}]}, ... ] (需要去重)

参考答案:

153. 扑克牌问题

参考答案:

154. 如何用 css 或 js 实现多行文本溢出省略效果,考虑兼容性

参考答案:

155. 实现一个 Dialog 类,Dialog可以创建 dialog 对话框,对话框支持可拖拽

参考答案:

156. 用 setTimeout 实现 setInterval,阐述实现的效果与setInterval的差异

参考答案:

157. 求两个日期中间的有效日期

参考答案:

158. (算法题)求多个数组之间的交集

参考答案:

159. 手写二进制转base64(阿里)

参考答案:

160. (算法题)求多个数组之间的交集

参考答案:

161. Async/Await 如何通过同步的方式实现异步

参考答案:

162. 模拟实现一个 localStorage

参考答案:

163. 模拟 localStorage 时如何实现过期时间功能

参考答案:

164. 考虑到性能问题,如何快速从一个巨大的数组中随机获取部分元素

参考答案:

165. 写一个单向链数据结构的 js 实现并标注复杂度

参考答案:

166. 使用 JavaScript Proxy 实现简单的数据绑定

参考答案:

167. 算法题「移动零」,给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

参考答案:

168. 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log(m n))。

参考答案:

169. 某公司 1 到 12 月份的销售额存在一个对象里面,如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。

参考答案:

170. 模拟实现一个 Promise.finally

参考答案:

171. ES6 代码转成 ES5 代码的实现思路是什么

参考答案:

172. 随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11, 20],将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4, 5], [10, 11], [20]]。

参考答案:

173. 如何把一个字符串的大小写取反(大写变小写小写变大写),例如 AbC 变成 aBc 。

参考答案:

174. 实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。

参考答案:

175. setTimeout、Promise、Async/Await 的区别

参考答案:

176. 请把俩个数组 [A1, A2, B1, B2, C1, C2, D1, D2] 和 [A, B, C, D],合并为 [A1, A2, A, B1, B2, B, C1, C2, C, D1, D2, D]。

参考答案:

177. 实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现

参考答案:

178. 使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果

参考答案:

179. 怎样实现千位分隔符

参考答案:

180. 请写一个正则,去除掉html标签字符串里的所有属性,并保留src和href两种属性

参考答案:

181. (开放题)2万小球问题:在浏览器端,用js存储2万个小球的信息,包含小球的大小,位置,颜色等,如何做到对这2万条小球信息进行最优检索和存储

参考答案:

182. 如何遍历一个dom树

参考答案:

183. 实现Array数组的flat方法

参考答案:

184. 获取元素的最终background-color

参考答案:

185. 10w 条记录的数组,一次性渲染到页面上,如何处理可以不冻结UI?

参考答案:

186. 说下 [1, 2, 3].map(parseInt) 结果

参考答案:[1, NaN, NaN]

187. 写一个发布订阅 EventEmitter方法

参考答案:

188. 使用setTimeout代替setInterval进行间歇调用

参考答案:

189. 实现一个私有变量

参考答案:

190. 实现一个instanceOf

参考答案:

191. 实现一个JS函数柯里化

参考答案:

192. 手写一个继承

参考答案:

193. 实现一个JSON.parse

参考答案:

194. 实现一个JSON.stringify

参考答案:

195. 实现一个new操作符

参考答案:

196. 实现一个new操作符

参考答案:

197. 基本数据类型和引用类型在存储上的差别

参考答案:

198. 如何实现一个 apply 函数?

参考答案:

199. 如何实现一个 call 函数?

参考答案:

200. 如何实现一个 bind 函数?

参考答案:

201. 题目如下:可使用任意前端框架,实现一个页面

页面上至少包含两个element
一个input输入框,一个output[用什么容器,标签都可以]
input要求输入一个数,表示图形面积
output要求输出一个六芒星[必须自己用css+html画出来,不能使用第三方],六芒星面积是input的数值。
当input发生改变时,输出图形使用css3新特性进行过渡。
【请注意考虑边界条件和异常输入】
加分项:input可以处理汉字表示的数字如:十一 十二

参考答案:

202. 下面的代码输出什么?

var y = 1;
if (function f() {}) {
    y += typeof f;
}
console.log(y);

参考答案:1undefined

JavaScript中if语句求值其实使用eval函数,eval(function f(){}) 返回 function f(){} 也就是 true。

下面我们可以把代码改造下,变成其等效代码。

var k = 1;
if (1) {
    eval(function foo() {});
    k += typeof foo;
}
console.log(k);

上面的代码输出其实就是 1undefined。为什么那?我们查看下 eval() 说明文档即可获得参考答案

该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。

恰恰 function f(){} 语句的返回值是 undefined,所以一切都说通了。

注意上面代码和以下代码不同。

var k = 1;
if (1) {
    function foo() {};
    k += typeof foo;
}
console.log(k); // output 1function

203. 写一个mul函数,使用方法如下。

console.log(mul(2)(3)(4)); // output : 24 
console.log(mul(4)(3)(4)); // output : 48

参考答案:

function mul(x) {
    return function(y) { // anonymous function 
        return function(z) { // anonymous function 
            return x * y * z;
        };
    };
}

简单说明下: mul 返回一个匿名函数,运行这个匿名函数又返回一个匿名函数,最里面的匿名函数可以访问 x, y, z 进而算出乘积返回即可。

204. 下面代码输出什么?

var output = (function(x) {
    delete x;
    return x;
})(0);

console.log(output);

参考答案:输出是 0。 delete 操作符是将object的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。

205. 下面代码输出什么?

var x = {
    foo: 1
};
var output = (function() {
    delete x.foo;
    return x.foo;
})();

console.log(output);

参考答案:输出是 undefined。x虽然是全局变量,但是它是一个object。delete作用在x.foo上,成功的将x.foo删去。所以返回undefined

206. 下面代码输出什么?

var Employee = {
    company: 'xyz'
}
var emp1 = Object.create(Employee);
delete emp1.company
console.log(emp1.company);

参考答案:输出是 xyz,这里的 emp1 通过 prototype 继承了 Employee的 company。emp1自己并没有company属性。所以delete操作符的作用是无效的。

207. 下面代码输出什么?

var trees = ["xyz", "xxxx", "test", "ryan", "apple"];
delete trees[3];

console.log(trees.length);

参考答案:输出是5。因为delete操作符并不是影响数组的长度。

208. 下面代码输出什么?

var bar = true;
console.log(bar + 0);
console.log(bar + "xyz");
console.log(bar + true);
console.log(bar + false);

参考答案:

1
truexyz
2
1

209. 下面代码输出什么?

var z = 1,
    y = z = typeof y;
console.log(y);

参考答案:输出是 undefined。js中赋值操作结合律是右至左的 ,即从最右边开始计算值赋值给左边的变量。

210. 下面代码输出什么?

var foo = function bar() {
    return 12;
};
typeof bar();

参考答案:输出是抛出异常,bar is not defined。

如果想让代码正常运行,需要这样修改代码:

var bar = function() {
    return 12;
};
typeof bar();

或者是

function bar() {
    return 12;
};
typeof bar();

明确说明这个下问题

var foo = function bar() {
    // foo is visible here 
    // bar is visible here
    console.log(typeof bar()); // Work here :)
};
// foo is visible here
// bar is undefined here

207. 下面代码输出什么?

var salary = "1000$";

(function() {
    console.log("Original salary was " + salary);

    var salary = "5000$";

    console.log("My New Salary " + salary);
})();

参考答案:

Original salary was undefined
My New Salary 5000$

这题同样考察的是变量提升。等价于以下代码

var salary = "1000$";
(function() {
    var salary;
    console.log("Original salary was " + salary);
    salary = "5000$";
    console.log("My New Salary " + salary);
})();

208. 什么是 instanceof 操作符?下面代码输出什么?

function foo() {
    return foo;
}

console.log(new foo() instanceof foo);

instanceof操作符用来判断是否当前对象是特定类的对象。

参考答案:返回 false

function Animal() {
    //或者不写return语句
    return this;
}
var dog = new Animal();
dog instanceof Animal // Output : true

209. 如果我们使用JavaScript的"关联数组",我们怎么计算"关联数组"的长度?

var counterArray = {
    A: 3,
    B: 4
};
counterArray["C"] = 1;

参考答案:其实参考答案很简单,直接计算key的数量就可以了。

Object.keys(counterArray).length // Output 3

210. 针对json数组,根据某一个值排序

参考答案:

var arrJson = [{
        flight: "ERWIO",
        price: 350
    },
    {
        flight: "WW250",
        price: 120
    },
    {
        flight: "QQ350",
        price: 100
    },
    {
        flight: "SDJIN",
        price: 300
    },
    {
        flight: "MH370",
        price: 120
    }
];

function sortByKey(array, key) {
    return array.sort(function(a, b) {
        var x = a[key];
        var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}
arrJson = sortByKey(arrJson, 'price');

211. 下面代码输出什么?

var a = {};
b = {
    key: 'b'
};
c = {
    key: 'c'
};
a[b] = 123;
a[c] = 456;
console.log(a[b]);

参考答案:456

首先a声明为一个对象,b和c也是对象,执行a[b] = 123时,b会转成字符串(调用toString()方法)来当作a对象的键,b.toString() === '[object Object]',所以a对象此时是这样的 a = {'[object Object]':123},同理可知,c也是一个对象,所以a[c] = 456执行,其实也是a['[object Object]'] = 456,把原本的123覆盖了,故输出a[b]也就是输出a['[object Object]'],输出456。

212. 下面代码输出什么?

(function(x){
    return (function(y){
        console.log(x);
    })(2)
})(1);

参考答案:1

很简单,一个自执行函数返回了一个自执行函数,所以二者都会顺利执行,外层函数1当作x传入,内层函数2当作2传入,内层函数可以访问外层函数的变量,打印的x就是1 。

213.变量提升

// 1
console.log(tt);
tt = "dd";
console.log(tt);
// 变量提升之后的代码:
var tt;
console.log(tt); //undefined
tt = "dd";
console.log(tt); //'dd'
// 2
if (!a) {
  var a = 2;
}
console.log(a);
// 变量提升之后的代码:
var a; //undefined
if (!a) {
  //true
  a = 2;
}
console.log(a); //2

214.函数提升

// 1
if (false) {
  function fn() {
    console.log(1);
  }
}
console.log(fn);
fn();
// 变量提升之后的代码:
var fn; //undefined
if (false) {
  function fn() {
    console.log(1);
  }
}
console.log(fn); //undefined
fn(); // fn is not a function
// 2
function fn() {
  foo();
  return;
  function foo() {}
}
fn();
// 变量提升之后的代码:
function fn() {
  function foo() {}
  foo(); // 没有输出也不会报一个错误,因为foo是一个函数
  return;
}
fn();
// 3
function bar() {
  console.log(foo);
  return;
  var foo = function() {};
}
bar();
// 变量提升之后的代码:
function bar() {
  var foo;
  console.log(foo); // undefined
  return; // 函数return之后的代码依旧会发生变量提升
  foo = function() {};
}
bar();
// 4
console.log(f1);
console.log(f2);
function f1() {}
var f2 = function() {};
// 变量提升之后的代码:
function f1() {} // 函数提升,整个代码块提升到文件的最开始
var f2;
console.log(f1); // function f1() {}
console.log(f2); // undefined
f2 = function() {};

215.函数和变量同时提升

// 1
console.log(fn);
var fn = function() {
  console.log(1);
};
console.log(fn);
function fn() {
  console.log(2);
}
console.log(fn);
// 变量提升之后的代码:
var fn;
function fn() {
  console.log(2);
}
console.log(fn); // 2
fn = function() {
  console.log(1);
};
console.log(fn); // 1
console.log(fn); // 1
// 2
console.log(f1());
console.log(f2);
function f1() {
  console.log("aa");
}
var f2 = function() {};
// 变量提升之后的代码:
var f2
function f1() {
  console.log("aa"); // "aa"
}
f2 = function() {};
console.log(f1()); // undefined
console.log(f2); // ƒ () {}
// 3
(function() {
  console.log(a);
  a = "aaa";
  var a = "bbb";
  console.log(a);
})();
// 变量提升之后的代码:
// 4
console.log(a);
var a = 1;
console.log(a);
function a() {}
console.log(a);
// 变量提升之后的代码: 函数的提升后的位置是在变量提升后的位置之后的
var a;
function a() {}
console.log(a); // a()
a = 1;
console.log(a); // 1
console.log(a); // 1
// 5
console.log(a);
var a = 1;
console.log(a);
function a() {}
console.log(a);
console.log(b);
var b = 2;
console.log(b);
function b() {}
console.log(b);
// 变量提升之后的代码:
var a;
var b;
function a() {}
function b() {}
console.log(a); // a()
a = 1;
console.log(a); // 1
console.log(a); // 1
console.log(b); // b()
b = 2;
console.log(b); // 2
console.log(b); // 2
// 6
console.log(a);
console.log(b); // 报错 隐式全局变量不会提升
b = "aaa";
var a = "bbb";
console.log(a);
console.log(b);

216.原型链面试题

参考答案:

// 1
function A() {}
function B() {}
B.prototype = new A();
var a = new A();
B.prototype = a;
var b = new B();
console.log(b.constructor); // 构造函数A

//2

console.log(Function.constructor === Function);
// Function 是一个构造函数
console.log(Function.__proto__.constructor === Function);

// 默认原型上面的constructor属性指向了原型所在的构造函数。

console.log(Object.constructor === Function);
// Object本身没有constructor这个属性,那么就到它的原型链上去查找,
Object.__proto__ === Function.prototype;

console.log(Function.__proto__.__proto__ === Object.prototype);
Function.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;

217.原型链面试题

参考答案:

var o = new Object();
function foo(obj) {
  obj.name = "腐女";
  obj = new Object();
  obj.name = "屌丝"; //这是另一个新对象的name
}
foo(o);
console.log(o.name); //想要的是原来对象的name,所以输出腐女

218.箭头函数问题

参考答案:

第 1 题

在使用=>定义函数的时候,this 的指向是定义时所在的对象,而不是使用时所在的对象;

class Animal {
  constructor() {
    this.type = "animal";
  }
  say(val) {
    setTimeout(function() {
      console.log(this); //window
      console.log(this.type + " says " + val);
    }, 1000);
  }
}
var animal = new Animal();
animal.say("hi"); //undefined says hi
class Animal {
  constructor() {
    this.type = "animal";
  }
  say(val) {
    setTimeout(() => {
      console.log(this); //Animal
      console.log(this.type + " says " + val);
    }, 1000);
  }
}
var animal = new Animal();
animal.say("hi"); //animal says hi

第二题:

箭头函数里面根本没有自己的 this,而是引用外层的 this

// ES6
function foo() {
  setTimeout(() => {
    console.log("id:", this.id);
  }, 100);
}

// 转成ES5
function foo() {
  var _this = this;

  setTimeout(function() {
    console.log("id:", _this.id);
  }, 100);
}

219.把以下代码使用两种方法,依次输出 0-9

参考答案:

var funcs = [];
for (var i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i);
  });
}
funcs.forEach(function(func) {
  func(); //输出十个10
});

解决办法:

方法一:使用立即执行函数

var funcs = [];
for (var i = 0; i < 10; i++) {
  funcs.push(
    (function(value) {
      return function() {
        console.log(value);
      };
    })(i)
  );
}
funcs.forEach(function(func) {
  func(); //依次输出0-9
});

方法二:使用闭包

function show(i) {
  return function() {
    console.log(i);
  };
}
var funcs = [];
for (var i = 0; i < 10; i++) {
  funcs.push(show(i));
}
funcs.forEach(function(func) {
  func(); //0 1 2 3 4 5 6 7 8 9
});

方法三:使用 let

var funcs = [];
for (let i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i);
  });
}
funcs.forEach(function(func) {
  func(); //依次输出0-9
});

220.手写一个 promise

参考答案:

var promise = new Promise((resolve, reject) => {
  if (success) {
    // 操作成功
    resolve(value);
  } else {
    reject(error);
  }
});

promise
  .then(res => console.log(res))
  .catch(err => {
    console.log(err);
  });

221.怎么解决回调函数里面回调另一个函数,另一个函数的参数需要依赖这个回调函数。需要被解决的代码如下:

$http.get(url).success(function (res) {
  if (success != undefined) {
    success(res);
  }
}).error(function (res) {
  if (error != undefined) {
    error(res);
  }
});

function success(data) {
  if( data.id != 0 {
    var url = "getdata/data?id=" + data.id + "";
    $http.get(url).success(function (res) {
      showData(res);
    }).error(function (res) {
      if (error != undefined) {
        error(res);
      }
    });
  }
}

参考答案:使用 Promise/async/await 解决

解析:

function awaitMethod(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(2 * num); // 此处模拟接口的请求
    }, 2000);
  });
}
// 打个比方,await是学生,async是校车,必须等人齐了再开车
async function test() {
  let result = await awaitMethod(30); // await 这个关键字只能在使用async定义的函数里面使用
  console.log(result); // 2秒钟之后控制台输出60 ; 后面利用 result 继续调用函数
  let next = await awaitMethod(result);
  console.log(next); // 4秒钟之后控制台输出120
  return next;
}
// 在async里,必须要将结果return回来,不然的话.then .catch获取不到值
test()
  .then(success => console.log("成功", success))
  .catch(error => console.log("失败", error));

222.以下代码依次输出的内容是?

setTimeout(function() {
  console.log(1);
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for (var i = 0; i < 10000; i++) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

参考答案:打印顺序 2 3 5 4 1

解析:

首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。

然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。

然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。

因此,应当先输出 5,然后再输出 4 , 最后在到下一个 tick,就是 1 。

参考

223.jQuery 的 ajax 返回的是 promise 对象吗?

参考答案:

jquery 的 ajax 返回的是 deferred 对象,通过 promise 的 resolve()方法将其转换为 promise 对象。

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

224.promise 只有 2 个状态,成功和失败,怎么让一个函数无论成功还是失败都能被调用?

参考答案:

使用promise.all()

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

Promise.all方法接受一个数组作为参数,数组里的元素都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)

示例:
var p =Promise.all([p1,p2,p3]);
p的状态由p1、p2、p3决定,分为两种情况。
当该数组里的所有Promise实例都进入Fulfilled状态:Promise.all**返回的实例才会变成Fulfilled状态。并将Promise实例数组的所有返回值组成一个数组,传递给Promise.all返回实例的回调函数**。

当该数组里的某个Promise实例都进入Rejected状态:Promise.all返回的实例会立即变成Rejected状态。并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

225.Promise 编程题

参考答案:

第 1 题

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve();
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
运行结果及原因

运行结果:
1 2 4 3

原因:
Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

第 2 题

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  }, 1000);
});
const promise2 = promise1.then(() => {
  throw new Error("error!!!");
});

console.log("promise1", promise1);
console.log("promise2", promise2);

setTimeout(() => {
  console.log("promise1", promise1);
  console.log("promise2", promise2);
}, 2000);

运行结果及原因

运行结果:
promise1 Promise { <pending> }
promise2 Promise { <pending> }
Uncaught (in promise) Error: error!!!
promise1 Promise { 'success' }
promise2 Promise {
  <rejected> Error: error!!!
    at promise.then (...)
    at <anonymous> }


原因:
promise 有 3 种状态:pending(进行中)、fulfilled(已完成,又称为Resolved) 或 rejected(已失败)。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。上面 promise2 并不是 promise1,而是返回的一个新的 Promise 实例。

第 3 题

const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
});

promise
  .then(res => {
    console.log("then: ", res);
  })
  .catch(err => {
    console.log("catch: ", err);
  });
运行结果及原因

运行结果:
then:success1

原因:
构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,呼应代码二结论:promise 状态一旦改变则不能再变。

第 4 题

Promise.resolve(1)
  .then(res => {
    console.log(res); // 打印1
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res); // 打印2
  });

运行结果: 1 2

原因: promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。

第 5 题

Promise.resolve()
  .then(() => {
    return new Error("error!!!");
  })
  .then(res => {
    console.log("then: ", res);
  })
  .catch(err => {
    console.log("catch: ", err);
  });
运行结果
then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

原因
.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))。

第 6 题

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("once");
    resolve("success");
  }, 1000);
});

const start = Date.now();
promise.then(res => {
  console.log(res, Date.now() - start);
});
promise.then(res => {
  console.log(res, Date.now() - start);
});
运行结果:
once
success 1001
success 1001
注:1001不是准确数值,也可能是998、999、1000、1002 等

原因:
promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

第 7 题

const promise = Promise.resolve().then(() => {
  return promise;
});
promise.catch(console.error);
运行结果
TypeError: Chaining cycle detected for promise #<Promise>...

原因
.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

第 8 题

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log);
运行结果
1

原因
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

第 9 题

Promise.resolve()
  .then(
    function success(res) {
      throw new Error("error");
    },
    function fail1(e) {
      console.error("fail1: ", e);
    }
  )
  .catch(function fail2(e) {
    console.error("fail2: ", e);
  });
运行结果
fail2: Error: error
    at success (...)
    at ...

原因
.then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。.catch 是 .then 第二个参数的简便写法,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。

第 10 题

process.nextTick(() => {
  console.log("nextTick");
});
Promise.resolve().then(() => {
  console.log("then");
});
setImmediate(() => {
  console.log("setImmediate");
});
console.log("end");
运行结果
end
nextTick
then
setImmediate

原因
process.nextTick 和 promise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。

226.看题算结果

var tasks = []; // 这里存放异步操作的 Promise
var output = i =>
  new Promise(resolve => {
    setTimeout(() => {
      console.log(new Date(), i);
      resolve();
    }, 1000 * i);
  });

// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
  tasks.push(output(i));
}

console.log(new Date, i);

参考答案:

Mon Aug 12 2019 09:37:36 GMT+0800 (中国标准时间) 5
Mon Aug 12 2019 09:33:55 GMT+0800 (中国标准时间) 0
然后每隔1s打印
Mon Aug 12 2019 09:33:56 GMT+0800 (中国标准时间) 1
Mon Aug 12 2019 09:33:57 GMT+0800 (中国标准时间) 2
Mon Aug 12 2019 09:33:58 GMT+0800 (中国标准时间) 3
Mon Aug 12 2019 09:33:59 GMT+0800 (中国标准时间) 4

解析:参考

227.看题算结果

// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = timeountMS =>
  new Promise(resolve => {
    setTimeout(resolve, timeountMS);
  });

(async () => {
  // 声明即执行的 async 函数表达式
  for (var i = 0; i < 5; i++) {
    await sleep(1000);
    console.log(new Date(), i);
  }

  await sleep(1000);
  console.log(new Date(), i);
})();

参考答案:每隔1s打印

Mon Aug 12 2019 09:39:02 GMT+0800 (中国标准时间) 0
Mon Aug 12 2019 09:39:03 GMT+0800 (中国标准时间) 1
Mon Aug 12 2019 09:39:04 GMT+0800 (中国标准时间) 2
Mon Aug 12 2019 09:39:05 GMT+0800 (中国标准时间) 3
Mon Aug 12 2019 09:39:06 GMT+0800 (中国标准时间) 4
Mon Aug 12 2019 09:39:07 GMT+0800 (中国标准时间) 5

解析:参考

228.简单实现async/await中的async函数

参考答案:

229.简单实现项目代码按需加载,例如import { Button } from antd,打包的时候只打包button

参考答案:

230.前端开发都应该懂的事件循环(event loop)以及异步执行顺序(setTimeout、promise和async/await)

参考答案:按着参考地址的文章,很容易

参考地址

来自文章结语

  1. JS是单线程执行的,同一时间只能处理一件事。但是浏览器是有多个线程的,JS引擎通过分发这些耗时的异步事件(AJAX请求、DOM操作等)给Web APIs线程处理,因此避免了单线程被耗时的异步事件阻塞的问题。

  2. Web APIs线程会将接收到的所有事件中已完成的事件根据类别分别将它们添加到相应的任务队列中。其中任务队列分以下两种:

    1. 宏任务队列(macrotask queue):其实是叫任务队列,ES5称task queue,也即本文图中的callback queue,macrotask是我们给它的别名,原因只是为了与ES6新增的microtask队列作区分而这样称呼,HTML标准中并没有macrotask这种说法。它存放的是DOM事件、AJAX事件、setTimeout事件等。
    2. 微任务队列(microtask queue):它存放的是Promise事件、nextTick事件等。优先级比macrotask高。
  3. 事件循环(event loop) 机制是为了协调事件(events)、用户交互(user interaction)、JS脚本(scripts)、页面渲染(rendering)、网络请求(networking)等等事件的有序执行而设置(定义由HTML标准给出,实现方式是靠各个浏览器厂商自己实现)。事件循环的过程如下:

    1. JS引擎执行一个事件,当遇到异步事件时则将其交给浏览器的Web APIs线程处理,然后该事件继续执行,永远不会被抢占,一直执行到该事件结束为止(run to complete)。
    2. 当JS引擎执行完当前事件(即执行栈变为空)之后,它会先去查看microtask队列,将microtask队列中的所有待执行事件全部执行完毕。
    3. 等微任务事件全部执行完毕后,再进行页面的渲染,此时表明一轮事件循环的过程结束。然后再去查看macrotask队列,取出一个宏事件添加到执行栈执行,开始一轮新的事件,执行完毕后再去执行所有微任务事件…如此往复。此即事件循环的执行过程。

打个比方帮助理解:宏任务事件就像是普通用户,而微任务事件就像是VIP用户,执行栈要先把所有在等待的VIP用户服务好了以后才能给在等待的普通用户服务,而且每次服务完一个普通用户以后都要先看看有没有VIP用户在等待,若有,则VIP用户优先(PS:人民币玩家真的可以为所欲为,hah…)。当然,执行栈正在给一个普通用户服务的时候,这时即使来了VIP用户,他也是需要等待执行栈服务完该普通用户后才能轮到他。

  1. setTimeout设置的时间其实只是最小延迟时间,并不是确切的等待时间。实际上最小延时 >=4ms,小于4ms的会被当做4ms。

  2. promise 对象是由关键字 new 及Promise构造函数来创建的。该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数(即 new Promise(...)中的...的内容)。这个“处理器函数”是在promise创建时是自动执行的,.then之后的内容才是异步内容,会交给Web APIs处理,然后被添加到微任务队列。

  3. async/await:async函数其实是Generator函数的语法糖(解释一下“语法糖”:就是添加标准以外的语法以方便开发人员使用,本质上还是基于已有标准提供的语法进行封装来实现的),async function 声明用于定义一个返回 AsyncFunction 对象的异步函数。执行async函数时,遇到await关键字时,await 语句产生一个promise,await 语句之后的代码被暂停执行,等promise有结果(状态变为settled)以后再接着执行。

以下是文章中的练习题

4.1 简单融合

//请写出输出内容
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
	console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});

console.log('script end');
输出的结果:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

自己打印成功

4.2 变形1

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
	    console.log('promise1');
	    resolve();
	}).then(function() {
	    console.log('promise2');
    });
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');
输出的结果:

script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout

自己的打印

script start
async1 start
promise1
promise3
script end
async1 end
promise2
promise4
setTimeout



async1 end
promise2
位置不对

4.3 变形2

async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
	setTimeout(function() {
		console.log('setTimeout2')
	},0)
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout3');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});

console.log('script end');
输出的结果:

script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1
自己的打印,后部分错了

script start
async1 start
promise1
script end
setTimeout2
setTimeout1
setTimeout3
promise2

4.4 变形3

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')
输出的结果:

script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout
自己的打印:后部分错了


script start
a1 start
a2
promise2
script end
promise1
promise2.then
promise3
a1 end
setTimeout

230.下面代码输出什么?

  console.log("同步任务1");
  function asyn(mac) {
      console.log("同步任务2");
      if (mac) {
          console.log(mac);
      }
      return new Promise((resolve, reject) => {
          console.log("Promise中的同步任务");
          resolve("Promise中回调的异步微任务")
      })
  }
  setTimeout(() => {
      console.log("异步任务中的宏任务");
      setTimeout(() => {
          console.log("定时器中的定时器(宏任务)");
      }, 0)
      asyn("定时器传递任务").then(res => {
          console.log('定时器中的:', res);
      })
  }, 0)
  asyn().then(res => {
      console.log(res);
  })
  console.log("同步任务3")

参考答案:

同步任务1 同步任务2 Promise中的同步任务 同步任务3 Promise中回调的异步微任务 异步任务中的宏任务 同步任务2 定时器传递任务 Promise中的同步任务 定时器中的: Promise中回调的异步微任务 定时器中的定时器(宏任务)

参考地址