这里记录一些在ubc暑期课程中学习js的一些难点,由于ubc讲课太快了,只能挑部分记录

声明变量关键字

这是一个很折磨的地方,因为js中的声明变量可以像python一样不用定义类型,但是同时又可以使用var, let等关键字进行定义,所以需要区分清楚

num=3 //全局定义
var num=3 //函数级定义
let num=3 //块级定义
const num=3 //块级常量定义

如果什么都不加,那么就是全局定义,可以在全局环境中被访问,这种定义方式及其不推荐,因为很容易出现命名冲突,污染等问题。var关键字定义的变量在函数级作用域中生效,所以所有函数都能访问到这个变量,因此容易出现数据意外覆盖的问题,因此也不推荐使用,let和const生效的区域是块级域,这意味着较难出现数据污染的问题。

回调

在js中,万物皆是对象,因此函数也是对象,它可以被当作参数放入另一个函数中,而在该函数执行完逻辑后调用这个传入函数的步骤,就是回调。

function randInt(){
return Math.floor(Math.random() * 2000);
}

function whenBothFinish (fn1, fn2, callback){

var fn1_done = false;
var fn2_done = false;

function checkFlagsAndCallback(){
if (fn1_done && fn2_done) callback();//执行回调函数
}

setTimeout(function(){
fn1();
fn1_done = true;
checkFlagsAndCallback();
}, randInt());

setTimeout(function(){
fn2();
fn2_done = true;
checkFlagsAndCallback();
}, randInt());

};

whenBothFinish(
function(){ console.log('fn1 finished!'); }, //fn1
function(){ console.log('fn2 finished!'); }, //fn2
function(){ console.log('Both functions finished!'); }//callback
);

console.log("Called whenBothFinish");

以上就是典型的回调例子,只有等到fn1和fn2都运行完毕后,才会执行callback的函数,这个方法也可以用在异步操作上。

闭包

闭包这个概念之前确实不懂,目前根据我的理解,闭包的意思就是在函数嵌套函数的结构中,内部函数具有访问外部函数中变量的能力。也就是说,当内部函数调用参数的时候,优先在本函数作用域中寻找参数,如果没有,则会访问上一层函数去找这个参数。而当我们的外部函数返回的值是内部函数的时候,内部函数调用的外部参数值会保留,这点非常有用,这意味着外部函数的参数不会随着该函数返回而消失,而是会记录上一次的值,举个例子:

function createCounter(initialValue) {
let count = initialValue;

function increment() {
count++;
console.log(count);
}

return increment;
}

const counter1 = createCounter(0);
const counter2 = createCounter(10);

counter1(); // 输出: 1
counter1(); // 输出: 2

counter2(); // 输出: 11

闭包陷阱

闭包这样子搞肯定是有问题的,很容易出现一些难以理解的问题,比如闭包陷阱,这个问题例子:

function addClickListeners (buttons){
for (var i = 0; i < buttons.length; i++){
buttons[i].addEventListener("click", function(){
alert("Clicked Button " + i);
});
}
return buttons;
};

var btns = document.getElementsByTagName("button");

addClickListeners(btns);

这个问题我很难理解,在我的认知中,每次循环绑定点击事件,应该是没有问题的,i循环到多少就绑多少。但是这里有一个闭包函数。先说结果,结果就是每一个按钮点击后显示的结果都是Clicked Button 4,即都是一样的。再说我的理解,这里有一个闭包函数,就是clicked后面定义的函数。我们要理解的就是实际上我们给按钮绑定的事件是函数,而非那一行字,意思就是我们点击按钮,实际上是调用了这个闭包函数,而在我们点击函数的时候,循环早就结束了(这样我们才能点击按钮。也就是说,按钮确实绑定了点击事件,但是这个点击事件不是Clicked Button 1(2/3/4),不是的,而是Clicked Button i,又因为闭包函数i共享的上一层函数的i,这样每次点击按钮,闭包函数调用i,而这个i,是上一层函数的i,而又因为上一层函数的i是var定义的,可作用于整个函数及其内部,所以这个i,他是共享的,这就导致了,i跟着循环走,走到哪一层,i就是什么,这样在点击按钮的时候,i早就走完了最后一层,那就是buttons.length。

这里就涉及到了闭包函数调用外层函数,var定义变量作用于函数作用域的问题,因此要想解决这个问题,主要思路就是:既然问题出在i共享了所有按钮,那么解决方案就是让它隔离开来,隔离的方法可以用let关键字,也可以用及时调用函数:

function addClickListeners(buttons) {
for (var i = 0; i < buttons.length; i++) {
(function(i) {
buttons[i].addEventListener("click", function(){
console.log("Clicked Button " + i);
});
})(i);
}
return buttons;
}

(function(i) { … })(i);这个及时调用方法可以立刻调用函数,也可以创建一个新的作用域,这意味着当我们点击按钮的时候,闭包函数先找的是上一个作用域,那就是这个立即调用函数的作用域,在这个作用域中的i就是先前处理得到的i,这样就可以隔绝开每一个i,避免让他们共享。而let的思路也是这样。