闭包、作用域和运行上下文 挑战1 createFunction 问题:
构建createFunction函数,用于创建和返回函数。当被创建的函数被调用时,它会打印“hello”。
题解: // CHALLENGE 1 function createFunction() {
const innerFunction = () => {
console.log("hello");
} return innerFunction; }
/ Uncomment these to check your work! / const function1 = createFunction(); function1(); // => should console.log('hello'); 挑战2 createFunctionPrinter 问题:
构建接受一个输入值作为参数的createFunctionPrinter函数,用于创建和返回一个特定函数。当特定函数被调用时,其应该打印特定函数被创建时输入createFunctionPrinter中的值。
题解: // CHALLENGE 2 function createFunctionPrinter(input) {
const innerValue = input;
const innerFunction = () => {
console.log(innerValue);
} return innerFunction; }
/ Uncomment these to check your work! / const printSample = createFunctionPrinter('sample'); printSample(); // => should console.log('sample'); const printHello = createFunctionPrinter('hello'); printHello(); // => should console.log('hello'); 挑战3 addByX 问题:
观察下面outer函数的实现代码。注意其会返回一个函数而且那个函数使用了不在其作用域的变量。尝试推断一下运行outer函数得到的输出值,然后构建addByX函数,其会返回一个接受一个输入值作为参数并与x相加的函数。
代码: // CHALLENGE 3 function outer() { let counter = 0; // this variable is outside incrementCounter's scope function incrementCounter () {
counter ++;
console.log('counter', counter);
} return incrementCounter; }
const willCounter = outer(); const jasCounter = outer();
// Uncomment each of these lines one by one. // Before your do, guess what will be logged from each function call.
/ Uncomment these to check your work! / willCounter(); willCounter(); willCounter();
jasCounter(); willCounter();
function addByX(x) {
let backpackValue = x ;
const innerFunction = (el) => {
return backpackValue + el;
} return innerFunction; }
/ Uncomment these to check your work! / const addByTwo = addByX(2); console.log(addByTwo(1)); // => should return 3 console.log(addByTwo(2)); // => should return 4 console.log(addByTwo(3)); // => should return 5
const addByThree = addByX(3); console.log(addByThree(1)); // => should return 4 console.log(addByThree(2)); // => should return 5
const addByFour = addByX(4); console.log(addByFour(4)); // => should return 8 console.log(addByFour(5)); // => should return 9 挑战4 once 问题:
构建once函数,接受参数为一个回调函数并返回一个特定函数。当特定函数被第一次调用时,其会调用回调函数并返回输出值。如果其不是被第一次调用,则特定函数仅仅返回第一次调用时得到的回调函数返回值,而不是再次运行回调函数。
题解: // CHALLENGE 4 function once(func) {
let counter = 0;
let innerValue = 0; const innerFunction = (el) => {
counter++;
if(counter == 1) {
innerValue = func(el);
return innerValue;
} else {
return innerValue;
}
} return innerFunction; }
/ Uncomment these to check your work! / const onceFunc = once(addByTwo); console.log(onceFunc(4)); // => should log 6 console.log(onceFunc(10)); // => should log 6 console.log(onceFunc(9001)); // => should log 6 挑战5 after 问题:
构建after函数,接受一个数字n和一个回调函数作为参数。回调函数需要在通过after构建的函数运行第n次时才被运行。
题解: // CHALLENGE 5 function after(count, func) {
let counter = count;
const innerFunction = () => {
counter--;
if(counter == 0){
func();
}
} return innerFunction; }
/ Uncomment these to check your work! / const called = function() { console.log('hello') }; const afterCalled = after(3, called); afterCalled(); // => nothing is printed afterCalled(); // => nothing is printed afterCalled(); // => 'hello' is printed 挑战6 delay 问题:
构建delay函数,接受一个回调函数作为第一个参数,一个数值n(单位为毫秒)作为第二个参数。delay函数被调用后,须经n毫秒后才运行回调函数。任何其他赋给特定函数的参数会在n毫秒后被回调函数使用。提示:研究setTimeout();
题解: // CHALLENGE 6 function delay(func, wait, ...args) { setTimeout(() => func(...args), wait); }
delay(called, 2000); // "hello" after 2 seconds. 挑战7 rollCall 问题:
构建rollCall函数,接受一个由名字组成的数组,返回一个特定函数。第一次调用特定函数时,其会打印数组的第一个名字。第二次调用时,打印第二个名字,一直下去,直到所有名字都被打印。当所有名字都被打印后,其会打印“Everyone accounted for”。
题解: // CHALLENGE 7 function rollCall(names) { const innerArray = new Array; for(let i=0; i<names.length; i++){
innerArray[i] = names[i];
}
const innerFunction = () => {
if(innerArray.length == 0){
console.log("Everyone accounted for");
} else {
console.log(innerArray.shift());
}
} return innerFunction; }
/ Uncomment these to check your work! / const rollCaller = rollCall(['Victoria', 'Juan', 'Ruth']) rollCaller() // => should log 'Victoria' rollCaller() // => should log 'Juan' rollCaller() // => should log 'Ruth' rollCaller() // => should log 'Everyone accounted for' 挑战8 saveOutput 问题:
构建saveOutput函数,接受一个回调函数(其接受一个参数)和一个字符串(充当密码)作为参数。saveOutput会返回一个特定函数,其作用与回调函数类似,只是当它的字符串参数与saveOutput函数的密码字符串参数相同时,特定函数会利用先前所有调用特定函数时的输入值作为对象的键,对应回调函数的输出值作为值,构建一个对象,并返回之。
题解: // CHALLENGE 8 function saveOutput(func, magicWord) {
const storedMagicWord = magicWord;
const storedInputArray = new Array; const storedOutputArray = new Array; const exitObject = new Object; let tempOutput = 0; const innerFunction = (el) => {
if(el !== storedMagicWord) {
storedInputArray.push(el);
tempOutput = func(el);
storedOutputArray.push(tempOutput);
return tempOutput;
} else {
if(storedInputArray.length == storedOutputArray.length) {
for(let i=0; i<storedInputArray.length; i++){
exitObject[storedInputArray[i]]=storedOutputArray[i];
}
return exitObject;
} else{
return 'error';
}
}
} return innerFunction; }
/ Uncomment these to check your work! / const multiplyBy2 = function(num) { return num * 2; }; const multBy2AndLog = saveOutput(multiplyBy2, 'boo'); console.log(multBy2AndLog(2)); // => should log 4 console.log(multBy2AndLog(9)); // => should log 18 console.log(multBy2AndLog('boo')); // => should log { 2: 4, 9: 18 } 挑战9 cycleIterator 问题:
构建cycleIterator函数,接受参数为一个数组并返回一个特定函数。特定函数不接受参数。当第一次调用时,特定函数返回数组的第一个元素。第二次调用时,返回第二个元素,一直下去。当特定函数已返回数组的最后一个元素后,下一次调用此特定函数时其返回数组的第一个元素,然后第二个元素,一直下去。
题解: // CHALLENGE 9 function cycleIterator(array) {
let counter = 0;
const innerFunction = () => {
counter++;
if(counter > array.length){
counter = 0;
return array[counter];
} else{
return array[counter-1];
}
} return innerFunction; }
/ Uncomment these to check your work! / const threeDayWeekend = ['Fri', 'Sat', 'Sun']; const getDay = cycleIterator(threeDayWeekend); console.log(getDay()); // => should log 'Fri' console.log(getDay()); // => should log 'Sat' console.log(getDay()); // => should log 'Sun' console.log(getDay()); // => should log 'Fri' 挑战10 defineFirstArg 问题:
构建defineFirstArg函数,接受参数为一个回调函数和一个数值。回调函数接受至少一个参数。defineFirstArg会返回一个特定函数。此特定函数会将defineFirstArg的数值参数作为回调函数的第一个参数,输入到特定函数的参数作为回调函数的第二个参数,并调用回调函数。
题解: // CHALLENGE 10 function defineFirstArg(func, arg) {
const innerFunction = (el) =>{
return func(arg, el);
} return innerFunction; }
/ Uncomment these to check your work! / const subtract = function(big, small) { return big - small; }; const subFrom20 = defineFirstArg(subtract, 20); console.log(subFrom20(5)); // => should log 15 挑战11 dateStamp 问题:
构建dateStamp函数,接受参数为一个回调函数并返回一个特定函数。特定函数会接受回调函数所需要的参数并传给回调函数,返回一个对象记录特定函数被调用时的日期信息(date键)以及回调函数的输出(output键)。提示:你可能需要研究一下如何获取Date对象中的信息。
题解: // CHALLENGE 11 function dateStamp(func) {
let dateInfo;
const innerObj = new Object; const innerFunction = (el) => {
dateInfo = new Date();
innerObj['date'] = dateInfo.toString();
innerObj['output'] = func(el);
return innerObj;
} return innerFunction; }
/ Uncomment these to check your work! / const stampedMultBy2 = dateStamp(n => n * 2); console.log(stampedMultBy2(4)); // => should log { date: (today's date), output: 8 } console.log(stampedMultBy2(6)); // => should log { date: (today's date), output: 12 } 挑战12 censor 问题:
构建censor函数,不接受参数。censor会返回一个接受两个字符串或单个字符串参数的特定函数。当得到两个字符串参数时,特定函数会存储两个字符串作为一对,以备后续使用。当得到一个字符串参数时,特定函数会返回同样的字符串,只是此字符串中与存储字符串对的第一个字符串相同的字符串会被替换成存储字符串对中的第二个字符串。
题解: // CHALLENGE 12 function censor() { let parameterInfoObj = new Object; let oneParameterArray = new Array; let punctuationArray = new Array;
const innerFunc = (el1, el2) => {
if(el1 && el2) {
oneParameterArray = [];
punctuationArray = [];
parameterInfoObj[el1] = el2;
return parameterInfoObj;
}
else if(el1 && !el2){
oneParameterArray = el1.match(/\b(\w+)\b/g);
punctuationArray = el1.match(/([,. ]+)/g);
for(let i = 0; i<oneParameterArray.length; i++){
for(let key in parameterInfoObj){
if(key === oneParameterArray[i]){
oneParameterArray[i] = parameterInfoObj[key];
}
}
}
for(let i =0; i<oneParameterArray.length; i++){
oneParameterArray[i] += punctuationArray[i];
}
return oneParameterArray.join("");
}
} return innerFunc; }
/ Uncomment these to check your work! / const changeScene = censor(); changeScene('dogs', 'cats'); changeScene('quick', 'slow'); console.log(changeScene('The quick, brown fox jumps over the lazy dogs.')); // => should log 'The slow, brown fox jumps over the lazy cats.' 挑战13 createSecretHolder 问题:
JavaScript对象中并没有私有属性的定义,然而或许可以自定义一个?构建createSecretHolder(secret)函数,接受任何值作为secret参数,并且仅返回两个方法,返回secret值的getSecret()和设置secret值的setSecret()。
题解: // CHALLENGE 13 function createSecretHolder(secret) {
let secretInfo = secret;
const innerObj = new Object; innerObj.getSecret = ()=>{
return secretInfo;
} innerObj.setSecret = (el)=>{
secretInfo = el;
} return innerObj;
}
/ Uncomment these to check your work! / const obj = createSecretHolder(5) console.log(obj.getSecret()) // => returns 5 console.log(obj.setSecret(2)) // => undefined console.log(obj.getSecret()) // => returns 2 挑战14 callTimes 问题:
构建callTimes函数,返回一个特定函数。特定函数应返回它被调用的次数信息。
题解: // CHALLENGE 14 function callTimes() {
let counter = 0;
const innerFunction = () => {
return ++counter;
} return innerFunction; }
/ Uncomment these to check your work! / let myNewFunc1 = callTimes(); let myNewFunc2 = callTimes(); console.log(myNewFunc1()); // => 1 console.log(myNewFunc1()); // => 2 console.log(myNewFunc2()); // => 1 console.log(myNewFunc2()); // => 2 挑战15 russianRoulette 问题:
构建russianRoulette函数,作用为接受一个数值参数(假设为n)并返回一个特定函数。此特定函数不接受输入参数,而且会在前n-1次调用时返回字符串“click“,在第n次调用时则返回字符串”bang“,n次以后再调用皆返回字符串”reload to play again”。
题解: // CHALLENGE 15 function russianRoulette(num) { let counter = num;
const innerFunction = () => {
counter--;
if(counter>0) {
return "click";
} else if(counter==0){
return "bang";
} else{
return "reload to play again";
}
}
return innerFunction;
}
/ Uncomment these to check your work! / const play = russianRoulette(3); console.log(play()); // => should log 'click' console.log(play()); // => should log 'click' console.log(play()); // => should log 'bang' console.log(play()); // => should log 'reload to play again' console.log(play()); // => should log 'reload to play again' 挑战16 average 问题:
构建average函数,不接受参数,并返回一个特定函数(接受一个数值作为参数或无参数)。当average创建的特定函数被输入数值参数的方式调用时,返回值为所有曾经被输入到特定函数的数值参数的平均值(重复数值视为分别的数值)。当特定函数被无参数的方式调用时,返回当前的平均值。如果特定函数在无参数方式调用时不曾被输入数值参数的方式调用过,则返回0。
题解: // CHALLENGE 16 function average() { let averageValue = 0;
let argumentCounter = 0;
let argumentSum = 0;
const innerFunction = (el) => {
if(el == undefined) {
return averageValue;
} else{
argumentCounter++;
argumentSum += el;
averageValue = argumentSum / argumentCounter;
return averageValue;
}
}
return innerFunction;
}
/ Uncomment these to check your work! / const avgSoFar = average(); console.log(avgSoFar()); // => should log 0 console.log(avgSoFar(4)); // => should log 4 console.log(avgSoFar(8)); // => should log 6 console.log(avgSoFar()); // => should log 6 console.log(avgSoFar(12)); // => should log 8 console.log(avgSoFar()); // => should log 8 挑战17 makeFuncTester 问题:
构建makeFuncTester函数,接受参数为一个二维数组(其中第二维数组仅含两个元素),返回一个接收回调函数作为参数的特定函数。当二维数组的每一个子数组的第一个元素输入到回调函数时都产生与第二个元素相同的返回值时,特定函数返回ture,否则特定函数返回false。
题解: // CHALLENGE 17 function makeFuncTester(arrOfTests) { const firstLayerArray = []; for(let i = 0; i<arrOfTests.length; i++){
let secondLayerArray = [];
for(let j = 0; j<arrOfTests[i].length; j++){
secondLayerArray.push(arrOfTests[i][j]);
}
firstLayerArray.push(secondLayerArray);
} const innerFunction = (callback) => {
for(let i=0; i<firstLayerArray.length; i++){
if(callback(firstLayerArray[i][0]) !== firstLayerArray[i][1]){
return false;
}
}
return true;
} return innerFunction; }
/ Uncomment these to check your work! / const capLastTestCases = []; capLastTestCases.push(['hello', 'hellO']); capLastTestCases.push(['goodbye', 'goodbyE']); capLastTestCases.push(['howdy', 'howdY']); const shouldCapitalizeLast = makeFuncTester(capLastTestCases); const capLastAttempt1 = str => str.toUpperCase(); const capLastAttempt2 = str => str.slice(0, -1) + str.slice(-1).toUpperCase(); console.log(shouldCapitalizeLast(capLastAttempt1)); // => should log false console.log(shouldCapitalizeLast(capLastAttempt2)); // => should log true 挑战18 makeHistory 问题:
构建makeHistory函数,接受一个数值参数n(充当限定值)并返回一个特定函数(接受字符串作为参数)。特定函数会存储限定个数为n的最近输入到此特定函数中的字符串参数历史(每次调用存储一次)。每次一个字符串被输入到特定函数中,特定函数会返回拼接“done”于此字符串后的新字符串(空格作为间隔)。然而,如果输入字符串为“undo”,特定函数会删除字符串参数历史中的最近字符串值,返回拼接“undone”于此最近字符串后的新字符串(空格作为间隔)。如果输入字符串“undo”时特定函数中的字符串参数历史已为空,那么特定函数会返回字符串“nothing to undo”。
题解: // CHALLENGE 18 function makeHistory(limit) {
const limitNumber = limit;
let parameterStack = []; const innerFunction = (stringElement) => {
if(stringElement === "undo"){
if(parameterStack.length == 0) {
return "nothing to undo";
} else{
return `${parameterStack.pop()} undone`;
}
} else{
parameterStack.push(stringElement);
if(parameterStack.length > limit){
parameterStack = parameterStack.slice(-limit);
}
return `${stringElement} done`;
}
} return innerFunction; }
/ Uncomment these to check your work! / const myActions = makeHistory(2); console.log(myActions('jump')); // => should log 'jump done' console.log(myActions('undo')); // => should log 'jump undone' console.log(myActions('walk')); // => should log 'walk done' console.log(myActions('code')); // => should log 'code done' console.log(myActions('pose')); // => should log 'pose done' console.log(myActions('undo')); // => should log 'pose undone' console.log(myActions('undo')); // => should log 'code undone' console.log(myActions('undo')); // => should log 'nothing to undo' 挑战19 blackjack 问题:
仔细观察测试代码如果你需要帮忙来理解下面的算法描述。
构建blackjack函数,接受参数为一个数组(元素皆为从1到11的数值),返回一个DEALER函数。 DEALER函数会接受两个参数(皆为数值),然后返回一个另外的PLAYER函数。
在第一次调用PLAYER函数时,它会返回输入DEALER函数中的两个数值参数之和。
在第二次调用PLAYER函数时,它会返回下列两种情况中的一种:
输入blackjack函数的数值型数组的第一个数值加上输入DEALER函数中的两个数值参数之和得到的和,如果和小于等于21,返回此和; 如果和大于21,返回字符串“bust”。 如果第二次调用PLAYER函数时已返回”bust”,则接下来PLAYER函数的每次调用皆会返回字符串“you are done!”(不过不同于“bust”,输出”you are done!“时不会使用数值型数组中的数值)。如果第二次调用PLAYER函数时并未返回”bust“,则接下来调用PLAYER函数时会返回下列两种情况中的一种:
最近一次的和值加上数值型数组的下一个数值元素,如果这个求和结果小于等于21的话,返回此和; 返回“bust”如果求和结果大于21。 再次声明,如果其返回”bust”,则接下来PLAYER函数的每次调用皆会返回字符串“you are done!”,否则,PLAYER函数会继续使用最近一次和值与数值型数组的下一个数值元素求和,一直下去。
你可以假设给定的数值型数组有足够多的数值元素从而会在用完数组元素之前得到“bust”。
题解:
// CHALLENGE 19 function blackjack(array) {
const dealer = (num1, num2) => {
let first = true;
let bust = false;
let sum = num1 + num2;
const player = () => {
if(first) {
first = false;
return sum;
}
if(bust) {
return "you are done!";
}
if(sum + array[0] <= 21){
sum += array.shift();
return sum;
} else {
array.shift();
bust = true;
return "bust";
}
}
return player;
} return dealer; }
/ Uncomment these to check your work! /
/ DEALER / const deal = blackjack([2, 6, 1, 7, 11, 4, 6, 3, 9, 8, 9, 3, 10, 4, 5, 3, 7, 4, 9, 6, 10, 11]);
/ PLAYER 1 / const i_like_to_live_dangerously = deal(4, 5); console.log(i_like_to_live_dangerously()); // => should log 9 console.log(i_like_to_live_dangerously()); // => should log 11 console.log(i_like_to_live_dangerously()); // => should log 17 console.log(i_like_to_live_dangerously()); // => should log 18 console.log(i_like_to_live_dangerously()); // => should log 'bust' console.log(i_like_to_live_dangerously()); // => should log 'you are done!' console.log(i_like_to_live_dangerously()); // => should log 'you are done!'
/ BELOW LINES ARE FOR THE BONUS /
/ PLAYER 2 / const i_TOO_like_to_live_dangerously = deal(2, 2); console.log(i_TOO_like_to_live_dangerously()); // => should log 4 console.log(i_TOO_like_to_live_dangerously()); // => should log 15 console.log(i_TOO_like_to_live_dangerously()); // => should log 19 console.log(i_TOO_like_to_live_dangerously()); // => should log 'bust' console.log(i_TOO_like_to_live_dangerously()); // => should log 'you are done! console.log(i_TOO_like_to_live_dangerously()); // => should log 'you are done!
/ PLAYER 3 / const i_ALSO_like_to_live_dangerously = deal(3, 7); console.log(i_ALSO_like_to_live_dangerously()); // => should log 10 console.log(i_ALSO_like_to_live_dangerously()); // => should log 13 console.log(i_ALSO_like_to_live_dangerously()); // => should log 'bust' console.log(i_ALSO_like_to_live_dangerously()); // => should log 'you are done! console.log(i_ALSO_like_to_live_dangerously()); // => should log 'you are done!'