1、循环绑定事件
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
var nodes = document.querySelectorAll('ul li');
for ( var i = 0,len = nodes.length; i < len; i++) {
nodes[i].addEventListener('click', function(){
alert('click' + i)
})
}
解答: 结果是出现5个5,因为var声明变量是不会有局部作用域的,这里的 i 被提升到了当前函数作用域头部。js是单线程的,先执行主程序,就是先执行循环,再执行内部函数,此时i已经是5了
修改:
- 使用闭包,产生了局部作用域,将index保留了下来。就是要实现块级作用域,需要在原来的函数作用域上包裹上一层,即在需要限制变量提升的地方手动设置一个变量来替代原来的全局变量。
//闭包1
(function (node, index) {
nodes[i].addEventListener('click', function(){
alert('click' + index)
})
})(nodes[i], i)
//闭包2
!function (node, index) {
nodes[i].addEventListener('click', function(){
alert('click' + index)
})
}(nodes[i], i)
- ES5的forEach方法(使用slice将nodes转为真正的数组,把下标保留了下来)
[].slice.call(nodes).forEach((node, index) => {
node.addEventListener('click', function(){
alert('click' + index);
})
});
- 块级作用域变量声明let
for ( let i = 0,len = nodes.length; let < len; let++) {
//这里的li就属于当前块级作用域
nodes[i].addEventListener('click', function(){
alert('click' + i)
})
}
- 当列表的数据非常多的时候可以使用事件代理
2、事件代理
- 事件代理解决了什么问题
- 事件代理就是在父节点上处理子元素上触发的事情。注册在父元素上,事件会冒泡到子元素上。currentTarget指向的是绑定事件的元素,target指向的当前执行的对象(IE浏览器用event.srcElement)
- 如果列表中的数据很多,每个都绑定事件的话,就需要不断的与dom节点进行交互,访问的dom次数越多就会影响页面的重绘与重排,影响页面的交互时间,降低性能。
- 如果使用事件代理的话就可以只对一个对象进行操作
- 普通的时间绑定的话,当里边的元素是动态的时候,新添加的元素是没有事件的,但是使用代理的话只要是父级下的元素都有事件
- 事件代理的基本原理是什么(事件模型)
- 通过事件流机制完成的(捕获、触发、冒泡),给里边的li绑定事件的时候就会冒泡到父级,从而触发事件
- 事件代理与普通事件的优劣点
- 优点: 处理的函数少,内存分配少,更加高效
- 缺点:首先确定事件是有冒泡的特性的,否则不能使用事件代理。mousemove和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易
3、this对象
var a = {
a: 'haha',
getA: function () {
console.log(this.a);
}
}
var b = {
a: 'hello'
}
var getA = a.getA;
var getA2 = getA.bind(a);
function run (fn) {
fn()
}
a.getA();
getA();
run(a.getA);
getA2.call(b);
解答: 分别是 :‘haha’、a对象、a对象、‘haha’
- 第一个就是调用对象的方法
- 第二个就是一般的函数调用,默认this指向的是全局,所以是a对象。(但是严格模式下会报错)
- 第三个和第二个类似
- 第四个和call(b)没有什么关系,.bind之后就会生成新的函数,this对象已经绑向了a对象。 bind()最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值,但是参数是调用bind和call时的叠加
4、原型
原型是直接的对象与对象的关系
function clone(obj) {
//实现以下内容
}
var a = {name: 'a'};
var b = clone(a);
console.log(b.name); //a
a.name = 'a1';
console.log(b.name); //a1
b.name = 'b';
console.log(b.name); //b
a.name = 'a2';
console.log(b.name); //b
解答: 此题考查的是原型,当b上没有name属性的时候就会去原型找,如果原型有的话就返回原型上的,而且只能修改本身的属性,不能修改原型的属性。
//方法1
function clone(obj) {
return Object.create(obj) //创建对象并把对象的原型返回
}
//方法2
function clone(obj) {
var ret = {};
ret.__proto__ = obj;
return ret;
}
//方法3
function clone(obj) {
function o() {};
o.prototype = obj;
return new o;
}
5、异步
题目: 现在有三个异步接口,分别是
1、getTodayUser: 获取当天用户id,callback返回userId
2、getTodayMovie:获取当天的电影id,callback返回
3、bookMovieForUser:为用户预定电影,参数是userId和movieId
现在要实现,一个接口bookTodayMovieForTodayUser,为当天用户预定当天的电影
就是下边这样:
getTodayUser(function(userId){
//获取当天的预定客户
})
getTodayMovie(function(movieId){
//获取当天的电影
})
bookMovieForUser(userId, movieId, function(isDone){
//为用户预定影片
})
//根据以上接口封装这个函数
bookTodayMovieForTodayUser(function(isDone){
//为今天预定客户预定当天影片
alert('预定成功')
})
解答: 并行获取userId和movieId,然后才能预定电影
- 标准解答
function bookTodayMovieForTodayUser(callback) {
var params = []; // 标记完成的请求
function checkAndBook() {
var uid = params[0];
var mid = params[1];
if (typeof uid !== 'undefined' && typeof mid !== 'undefined') {
bookMovieForUser(uid, mid, callback);
}
}
getTodayUser(function(userId){
params[0] = userId
checkAndBook(); //检查
})
getTodayMovie(function(movieId){
params[1] = movieId
checkAndBook(); //检查
})
}
- 使用ES6的promise,使代码更通用
function convertToPromise(fn) {
return function() { //闭包
var args = [].slice.call(arguments);
var self = this;
return new Promise(function(resolve){
function callback(ret) {
resolve(ret)
}
args.push(callback);
fn.apply(self, args)
})
}
}
var getTodayUser2 = convertToPromise(getTodayUser);
var getTodayMovie2 = convertToPromise(getTodayMovie);
var bookMovieForUser2 = convertToPromise(bookMovieForUser);
Promise.all([getTodayUser2(), getTodayMovie2()]) //数组里的都被resolve的时候才会进入then,两个promise返回的参数会被结合在params里边
.then(function(params){
return bookMovieForUser2(params[0], params[1])
}).then(function(){
alert('done')
})