一、JavaScript的事件委托
在JavaScript中,事件委托可以让我们在父元素上监听事件,而不是在每个子元素上单独监听事件。这样做的好处是可以减少事件绑定的数量,提高性能。
事件委托的原理是利用事件冒泡,将子元素的事件冒泡到父元素上。通过判断触发事件的目标元素,来执行相应的操作。
1. 绑定事件
在父元素上绑定事件,比如在一个ul元素上监听所有li元素的点击事件:
let ulElement = document.querySelector('ul');
ulElement.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('clicked on li element');
}
});
上面的代码中,我们在ul元素上监听click事件,然后在回调函数中判断触发事件的目标元素是不是li元素。
2. 优点
事件委托的优点有:
减少事件绑定的数量,提高性能;
可以动态添加子元素,仍然能监听到事件。
3. 缺点
事件委托的缺点有:
如果父元素上绑定了太多事件,会影响性能;
对于不需要委托的子元素,还是会冒泡到父元素上,造成不必要的操作。
二、判断URL是否合法
在开发Web应用时,我们经常需要检测URL是否合法,可以使用正则表达式来实现。一个合法的URL通常包括以下部分:
协议(http或https);
域名(www.example.com);
端口号(可选);
路径(可选);
查询参数(可选);
锚点(可选)。
1. 使用正则表达式判断URL是否合法
下面是一个使用正则表达式判断URL是否合法的例子:
function isUrlValid(url) {
const regex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;
return regex.test(url);
}
console.log(isUrlValid('http://www.example.com')); // true
console.log(isUrlValid('https://www.example.com/path/to/file.html?q1=v1&q2=v2#anchor')); // true
console.log(isUrlValid('www.example.com')); // false
console.log(isUrlValid('example.com')); // false
上面的代码中使用了一个正则表达式来匹配URL。一个URL应该以http、https或ftp开头,后面跟着://,然后是任意非空白字符和非空格字符。如果有端口号,可以使用冒号加数字表示。路径、查询参数和锚点可以使用/、?、#来分隔。
2. 各部分详解
下面是对正则表达式的详细解释:
^(https?|ftp):// // 必须以http、https或ftp开头
[^\s/$.?#].[^\s]*$ // 中间部分,不能包含空格和特殊字符,可以包含-
(:\d+)? // 可选的端口号
(/[^\s]*)* // 可选的路径
(\?[\w&=]*)? // 可选的查询参数
(#.*)? // 可选的锚点
三、全排列
在计算机科学中,全排列是对一组数据进行排列的所有可能的方式。对于长度为n的序列,全排列的个数为n!(n的阶乘)。
1. 递归实现
function permute(input) {
let result = [];
function permuteHelper(input, temp) {
if (input.length === 0) {
result.push(temp.slice());
} else {
for (let i = 0; i < input.length; i++) {
let elem = input.splice(i, 1)[0];
temp.push(elem);
permuteHelper(input, temp);
input.splice(i, 0, elem);
temp.pop();
}
}
}
permuteHelper(input, []);
return result;
}
console.log(permute([1,2,3])); // [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
上面的代码通过递归实现全排列。递归函数permuteHelper接收两个参数:输入数组input和中间结果数组temp。在递归开始时,temp为空,input包含了所有要排列的元素。每次从input中选取一个元素加入temp中,然后递归地求解剩下的元素的全排列。递归结束时,将temp加入结果数组result,然后返回。
2. 非递归实现
下面是非递归实现全排列的代码:
function permute(input) {
let result = [];
let n = input.length;
let p = new Array(n).fill(0);
let i = 1;
let j;
result.push(input.slice());
while (i < n) {
if (p[i] < i) {
j = i % 2 && p[i];
[input[i], input[j]] = [input[j], input[i]];
result.push(input.slice());
p[i]++;
i = 1;
} else {
p[i] = 0;
i++;
}
}
return result;
}
console.log(permute([1,2,3])); // [[1,2,3],[2,1,3],[3,1,2],[1,3,2],[2,3,1],[3,2,1]]
上面的代码中,我们维护了一个记录每个元素的位置p数组。初始时,输入数组input作为第一个排列的结果,加入结果数组result。然后每次确定input中一个元素的位置,直到所有元素都确定。假设现在要确定第i个元素的位置,如果p[i]小于i,则将输入数组中第i个元素和第p[i]个元素交换位置,然后加入结果数组result。因为p[i]表示前i-1个元素已经全部交换过位置了,p[i]小于i意味着第i个元素还没有和前面的元素交换过位置。然后将p[i]加1,i设为1,继续确定下一个元素的位置。如果p[i]等于i,则说明第i个元素已经和前面所有元素都交换过位置了,将p[i]设为0,i加1,继续确定下一个元素的位置。
3. 性能比较
递归实现和非递归实现的时间复杂度都是 O(n!),但是非递归实现要比递归实现快得多,因为递归实现存在大量的函数调用和堆栈操作。非递归实现只需要维护一个数组,不需要额外的函数调用和堆栈操作,因此性能更好。