JS回调函数练习

最近看了一篇文章为:Mastering Hard Parts of JavaScript
里面主要包含以下部分:

  1. Callbacks & Higher order functions Closure
  2. scope and executioncontext
  3. JavaScript & the event loop
  4. Classes & Prototypes (OOP)

这篇文章列举了很多例子进行练习,有助于我进行学习。

我把这篇文章中我觉得很不错的例子列举出来,然后附上我自己的答案和自己的一些理解。

也欢迎大家一起思考,不要一开始看答案,先尝试自己去写,然后就会发现很多自己觉得很了解的知识点,其实都还有盲区,一点点尝试,一点点成长,每天一个小例子,真的会很开心哦~~。

另外要有其他的答案,欢迎评论区留言,(已完结)

下面开始正篇:

第一部分 Callbacks

Q1

写一个add函数,实现将数组中的值全部加2,其中加2的功能已经提供。
例如:console.log(add([1, 2, 3], addTwo)),输出结果应为[3,4,5]

function addTwo(num) {
    
    
    return num + 2
}
function add (arr,callback) {
    
    
  // 要写的代码
}
console.log(add([1, 2, 3], addTwo))

我的答案:

第一种:

function add(arr,callback) {
    
    
    let newArr = []
    for(let item of arr) {
    
    
        newArr.push(callback(item))
    }
    return newArr
}

第一种方式使用了for…of…的用法,这里要注意for…of输出的是数组的值,for…in输出的数组的索引,并且输出的索引是字符串哦。

for(let item in arr) {
    
    
        console.log(typeof item) //string,string,string
        console.log(item) // 0,1,2
    }

第二种:

function add(array, callback) {
    
    
    const newArr = [];
    array.forEach(item => {
    
    
      newArr.push(callback(item));
    });
    return newArr;
  }

第三种:

function add (arr,callback) {
    
    
    let newArray = arr.map(item => callback(item))
    return newArray
}

Q2

模拟累加器的功能,将数组的数值进行加和。注意:不可以直接使用reduce!
例如:数组为[4,1,3],输出的值为8

const nums = [4, 1, 3];
const add = function (a, b) {
    
    
  return a + b;
}
function reduce() {
    
    
  // 要写的代码
}
console.log(reduce(nums, add, 0))
console.log(reduce(nums, add))

我的答案:

function reduce(array,callback,initalValue) {
    
    
    let accum //定义一个累加器
    if(arguments.length > 2) {
    
     //有初始值
        accum = initalValue
    } else {
    
     //无初始值
        accum =  array[0]  
        array.shift() //数组第一个值为初始值,需要将第一个值排除
    }    
    array.forEach(item => {
    
          
        accum = callback(item,accum)
    });
    return accum
}

在这部分中我犯了两个错误:

  1. 我没有考虑到没有初始值的问题:其实else{accum = array[0]}的这部分代码也是看作者的答案才知道的。我们提供给别人一个函数功能时候,其实应该考虑全面,将多种情况都覆盖。另外,这个代码也还有问题,就是我没有加校验和报错,当其他人传一个参数,或者传入的参数不合规时,就会有问题。作为一个例子可以够用,但是作为要提供给其他人用的函数,就还有很多要完善的。
  2. 在我考虑了无初始值之后,添加了条件判断,但是我只考虑了accum = array[0],却忘了将已经作为初始值的数排除,即没有加array.shift()

看着代码没有多少,但是要考虑的问题很多。

Q3

将多个数组中共有的数值提取出来。
例如:[5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20],[5,12,1,15]); //输出结果为[5,15]

function intersection() {
    
    
  //代码部分
}
console.log(intersection([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20],[5,12,1,15]))

我的答案:

function intersection(...arrays) {
    
    
  let array =  arrays.reduce((totalArray,currentArray) => {
    
    
    return currentArray.filter(item => totalArray.includes(item))
  })
  return [...new Set(array)]
}

我的思考:

写这个答案时,我考虑用过map和filter结合的方式,不过后来发现循环好多啊,算了吧… 后来发现reduce和filter结合,真香~~,功能强大代码还少。

  1. 上一题模拟过reduce的简单用法,reduce里面用法有一项就是没有传入初始值时,取传入参数的第一个值。在这题中初始值就是arrays[0]
  2. 在传参数时候,因为不知道参数有多少个,用的扩展运算符(…)
  3. 之前有一个版本的代码,如下:
function intersection(...arrays) {
    
    
    return arrays.reduce((acc, array) => {
    
    
        return array.filter((item) => acc.includes(item));
      })
}

这个代码是可以实现题中的答案,但是当我们数组中有重复的数字,如console.log(intersection([5, 5,10, 15, 20], [15, 10, 1, 5, 7], [1, 10, 15, 5, 20],[5,12,1,15])),就会出现问题,因为没有去重。

Q4

返回一个数组,里面包含所有数组的元素,去除重复的元素
例如:[5, 10, 15], [15, 88, 1, 5, 7], [100, 15, 10, 1, 5],输出结果为[5, 10, 15, 88, 1, 7, 100]

function union() {
    
    
// 代码部分
}
console.log(union([5, 10, 15], [15, 88, 1, 5, 7], [100, 15, 10, 1, 5])) //[5, 10, 15, 88, 1, 7, 100]

我的答案:
第一种:

function union (...arrays) {
    
    
  const flatArray = arrays.flat()
  return [...new Set(flatArray)]
}

第二种:

function union(...arrays) {
    
    
  return arrays.reduce((totalArray,currentArray) => {
    
    
    let array = currentArray.filter(item => !totalArray.includes(item))
    return totalArray.concat(array)
  })
}

我的思考:
有了上一题的经验,这个功能实现就很容易

Q5

构造一个函数objOfMatches,它接受两个数组和一个回调。objOfMatches将构建一个对象并返回它。为了构建对象,objOfMatches将使用回调测试第一个数组的每个元素,以查看输出是否与第二个数组的相应元素(按索引)匹配。如果存在匹配项,则第一个数组中的元素将成为对象中的键,而第二个数组中的元素将成为对应的值。

例如:

function objOfMatches(array1, array2, callback) {
    
    
   //代码部分
 }
 
 console.log(
    objOfMatches(
      ["hi", "howdy", "bye", "later", "hello"],
      ["HI", "Howdy", "BYE", "LATER", "hello"],
      function (str) {
    
    
        return str.toUpperCase();
      }
    )
  )

输出结果为 { hi: ‘HI’, bye: ‘BYE’, later: ‘LATER’ }

我的答案:
第一种:

function objOfMatches(array1, array2, callback) {
    
    
  return array2.reduce((result,value,index)=>{
    
    
    if(value === callback(array1[index])) {
    
    
      result[array1[index]] = value
    }
    return result
  },{
    
    })
}

这个方法主要是为了练习reduce,以前很少使用这个方法,通过这些例子的不断练习,让我可以很熟练的使用了。

附上reduce的使用方法:

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
array:为需要遍历的数组
total:为必须值,是初始值或结果返回值
currentValue:为必须值,是当前元素
currentIndex: 为可选值,是当前元素索引
arr:为可选值,是当前元素所属的数组对象

第二种:

 function objOfMatches(array1, array2, callback) {
    
    
    let res = {
    
    }
    array2.filter((item,index) => {
    
    
      if (item === callback(array1[index])) {
    
    
        res[array1[index]] = item;
      } 
    })
    return res
  }

Q6

构造一个multiMap函数,该函数将接受两个数组:一个值数组和一个回调数组。multiMap将返回一个对象,其键与值数组中的元素匹配。分配给键的对应值将是由回调数组的输出组成的数组,其中每个回调的输入都是键。

例如:

function multiMap(array,callback){
    
    
   // 代码部分
}
console.log(
    multiMap(
      ["catfood", "glue", "beer"],
      [
        function (str) {
    
    
          return str.toUpperCase();
        },
        function (str) {
    
    
          return str[0].toUpperCase() + str.slice(1).toLowerCase();
        },
        function (str) {
    
    
          return str + str;
        },
      ]
    )
  );

应该输出 { catfood: [‘CATFOOD’, ‘Catfood’, ‘catfoodcatfood’], glue: [‘GLUE’, ‘Glue’, ‘glueglue’], beer: [‘BEER’, ‘Beer’, ‘beerbeer’] }

我的答案:

function multiMap(array,callback){
    
    
    return array.reduce((result,val)=>{
    
    
        result[val] = callback.map(fun => fun(val)) //map本身返回一个数组
        return result
    },{
    
    })
}

思考:
其实这个解决办法有很多,但是reduce和map的结合,可以让代码更加清晰简单,有时开发不能只满足于实现某个功能,更要考虑代码简易程度,运算复杂度,可读性等等,让代码更优

Q7

构造一个函数objectFilter,该函数接受一个对象作为第一个参数,并接受一个回调函数作为第二个参数。objectFilter将返回一个新对象。新对象将仅包含输入对象的属性,并且该属性的key等于传递给回调的属性的value

例如:

function objectFilter(obj,callback) {
    
    
 // 代码部分
}
const cities = {
    
    
    London: "LONDON",
    LA: "Los Angeles",
    Paris: "PARIS",
  };
  console.log(objectFilter(cities, (city) => city.toUpperCase()))

应该输出 { London: ‘LONDON’, Paris: ‘PARIS’}

我的答案:

function objectFilter(obj,callback) {
    
    
  let result = {
    
    }
  for(let item of Object.keys(obj) ) {
    
    
      if(callback(item) === obj[item]) {
    
    
        result[item] = obj[item]
      }
  }
  return result
}

思考:
这个答案主要利用了Object的一些方法,包括Object.keysObject.entries等等,这些方法可以将对象转为包含key或value的数组。

Q8

构造一个函数prioritize,该函数接受一个数组作为第一个参数,并接受一个回调函数作为第二个参数。回调函数将返回true或false。这个函数将该数组每个元素执行回调,并返回一个新数组,其中返回值true的元素在数组中排在前,其余元素排在后。
例如:

function prioritize(array,callback) {
    
    
    //代码
}
const startsWithS = function (str) {
    
    
    return str[0] === "s" || str[0] === "S";
  };
  console.log(
    prioritize(
      ["curb", "rickandmorty", "seinfeld", "sunny", "friends"],
      startsWithS
    )
  ); 

应输出:[‘sunny’, ‘seinfeld’, ‘curb’, ‘rickandmorty’, ‘friends’]

我的答案:

第一种:

function prioritize(array,callback) {
    
    
    return array.reduce((accum,item) => {
    
    
        callback(item) ? accum.unshift(item):accum.push(item)
        return accum
    },[]);
}

第二种:

function prioritize(array,callback) {
    
    
    let newArray = []
    array.forEach(item => {
    
    
        callback(item) ? newArray.unshift(item):newArray.push(item)
    });
    return newArray
}

我的思考:

这个方法主要使用unshift和push的方法,使用这种方式可以减少一些变量,代码也更加简洁。

Q9

构造一个函数groupBy,该函数接受一个数组作为第一个参数,并接受一个回调函数作为第二个参数。这个函数将该数组每个元素执行回调,并返回一个新对象,新对象的 key为回调的返回值,而与每个key相关联的value将是一个数组,这个数组包含所有传递给回调函数时导致该返回值的元素。
例如:

function groupBy(array,callback){
    
    
  //代码部分
}
const decimals = [1.3, 2.1, 2.4];
const floored = function (num) {
    
    
  return Math.floor(num);
};
console.log(groupBy(decimals, floored));

应输出:{ 1: [ 1.3 ], 2: [ 2.1, 2.4 ] }

我的答案:

function groupBy(array,callback){
    
    
    return array.reduce((accum,item)=>{
    
    
        // accum[callback(item)]?(accum[callback(item)].push(item)):(accum[callback(item)]=[item])
        accum.hasOwnProperty(callback(item))?(accum[callback(item)].push(item)):(accum[callback(item)]=[item])
        return accum
    },{
    
    })
}

在这个答案中有注释的部分,这两个写法都能实现这个功能,只不过我尝试用了hasOwnProperty()的写法,来判断当前对象是否有指定的key值。

Q10

创建一个函数pipe,该函数接受一个数组作为第一个参数,这个数组是由多个函数组成,并接受一个值(value)作为第二个参数。值输入到第一个参数中 的第一个函数中,然后将该函数的输出用作第二个函数的输入,然后将该函数的输出用作第三个函数的输入,依此类推,直到得到数组中最后一个函数的输出,函数应返回最终输出。
例如:

function pipe(array,value){
    
    
 //代码部分
}

const capitalize = (str) => str.toUpperCase();
const addLowerCase = (str) => str + str.toLowerCase();
const repeat = (str) => str + str;
const capAddlowRepeat = [capitalize, addLowerCase, repeat];
console.log(pipe(capAddlowRepeat, "cat"));

应输出 CATcatCATcat

我的答案:

function pipe(array,value){
    
    
  return array.reduce((accum,fn)=> {
    
    
      return fn(accum)
  },value)
}

这个结果代码其实很简短,但是需要前期对accum用法的熟练掌握

第二部分 Closure

这部分是对闭包进行练习

Q1

先来一个开胃小菜。

创建一个函数addByX,该函数会返回一个添加x输入的函数
例如:

// 3
function addByX(value) {
    
    
   // 代码部分
}

const addByTwo = addByX(2); 
console.log(addByTwo(1));
// => 输出为 3  3 = 2  + 1
console.log(addByTwo(2));
// => 4        4 = 2 + 2
console.log(addByTwo(3));
// => 5

const addByThree = addByX(3);
console.log(addByThree(1));
// =>  4     4 = 3 + 1
console.log(addByThree(2));
// =>  5

我的答案

function addByX(value) {
    
    
   return function addByNum(num) {
    
    
     return value + num
  }
}

这个就是一个很简单的闭包应用,将内部函数的值可以返给外部,实现外部作用域调用内部作用域。

Q2

编写一个函数,该函数接受两个参数,第一个参数是作为需要调用回调的次数value,第二个参数是需要进行的回调函数function。只有当执行回调的次数等于value时候,才会输出结果。

例如:

function after(value,func) {
    
    
   // 代码部分
}
const called = function () {
    
    
  console.log("hello");
};
const afterCalled = after(3, called);

afterCalled(); // => nothing is printed
afterCalled(); // => nothing is printed
afterCalled(); // => 'hello' is printed

我的答案:

function after(value,func) {
    
    
    value-=1
   return function afterCalled() {
    
    
      !value ? func() : value--
   }
}

这个例子我觉得还是有点意思,在这个代码里,为了少声明变量,减少一点内存,我使用的三元运算符;另外在开始时候将次数减少1次,这样就可以通过一个非运算符来判断是否达到了指定的次数。

另一种答案:

function after(count, func) {
    
    
  let counter = 0;
  function runAfter() {
    
    
    counter++;
    if (count === counter) {
    
    
      func();
    }
  }
  return runAfter;
}

这种答案也可以实现。

Q3

写函数延迟,它接受回调作为第一个参数,并等待毫秒(毫秒),然后才允许将回调作为第二个参数调用。在调用func时,会在wait之后提供任何其他参数。

例如:

function delay(func, wait, ...rest) {
    
    
    // 代码部分
  }
  
 function addThree(value) {
    
    
     console.log(value + 3)
  }

  delay(addThree,1000,2)

结果应该在1000ms之后才能 输出5

我的答案:

function delay(func, wait, ...rest) {
    
    
    function delayRun() {
    
    
      func(...rest);
    }
    setTimeout(delayRun, wait);
  }

Q4

编写一个函数rollCall,该函数接受数组并返回一个函数。第一次调用这个函数时,将第一个 名称记录到控制台。第二次调用它时,应将第二个名称记录到控制台,依此类推,直到调用了所有名称。调用完所有名称后,它应该记录“Everyone accounted for”。

例如:

function rollCall(array) {
    
    
  //代码部分
}
const rollCaller = rollCall(["Victoria", "Juan", "Ruth"]);
rollCaller(); // => should log 'Victoria'
rollCaller(); // => should log 'Juan'
rollCaller(); // => should log 'Ruth'
rollCaller(); // => should log 'Everyone accounted for'

我的答案:

第一种:

function rollCall(array) {
    
    
   return function caller() {
    
    {
    
    
    if(!array.length){
    
    
        console.log('Everyone accounted for')
    } else {
    
    
        let arr = array.shift() //shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
        console.log(arr)
    }
   }}
}

这种使用了shift的方法,他可以返回数组的第一个元素,但问题就是会改变数组,如果只是单纯要结果,这个方法还是很简单的

第二种:

function rollCall(array) {
    
    
    let counter = 0
    return function caller() {
    
    {
    
    
     if(counter === array.length) {
    
    
        console.log('Everyone accounted for')
     } else {
    
    
         console.log(array[counter])
         counter++
     }
    }}
 }

这个方法就是很正常的一种,定义一个变量来判断执行了几次,这种方法肯定不会改变原数组的。

Q5

创建一个不接受任何参数的censor函数。这个函数将返回一个接受两个字符串或一个字符串的函数。当给出两个字符串时,返回的函数将成对保存这两个字符串,以备将来使用。给定一个字符串时,返回的函数将返回字符串,但这个字符串中和第一个字符串(已保存对)相同的部分将被其对应的(已保存对)第二个字符串替换。

例如:

function censor() {
    
    
 // 代码部分
}
const changeScene = censor();
changeScene("dogs", "cats");
changeScene("quick", "slow");
console.log(changeScene("The quick, brown fox jumps over the lazy dogs."));

应输出 The slow, brown fox jumps over the lazy cats.

我的答案:

function censor() {
    
    
    let pair = new Map()
    let res = ''
    return function handleString(...args) {
    
    
        if (args.length === 2) {
    
    
            pair.set(args[0],args[1])
        } else {
    
    
            for(let [key,value] of pair) {
    
    
                reg = RegExp(key)
                res = args[0].replace(reg,value)
            }
        }
        return res
    }
}

我的思考:
这个例子还是有点意思的,

  1. 在这里使用了new Map方法进行存储,而不是直接使用object,用map有个好处就是这个对象是iterable,可以使用for..of..进行循环。
  2. 使用扩展运算符(…)将所有参数一起进行传入,注意这时候传进去的参数为数组
  3. 使用了String.replace()的用法进行内容替换,注意replace第一值需要是正则,所以这里我是用了RegExp(key) 将第一个参数值变为正则。

Q6

实现一个函数createSecretHolder(secret),该函数接受任何值作为secret并仅使用两种方法返回对象。getSecret()返回secret,setSecret()设置secret

例如:

// 13
function createSecretHolder(val) {
    
    
   // 代码部分
}
const obj = createSecretHolder(5);
console.log(obj.getSecret());
// => returns 5
obj.setSecret(2);
console.log(obj.getSecret());
// => returns 2

我的答案:

function createSecretHolder(val) {
    
    
    let num = val
    let obj = {
    
    
       getSecret() {
    
    
           return num
       },
       setSecret(value) {
    
    
           num = value
       }
   }
   return obj
}

代码很简短,但是却实现了简单的get和set的方法

Q7

创建一个函数makeFuncTester,该函数接受一个数组(包含两个元素的子数组),并返回一个函数(将接受回调)。如果传递到回调中的(每个子数组)的第一个元素都产生对应的(相同子数组的)第二个元素,则返回的函数应返回true。否则,返回的函数应返回false。

例如:

function makeFuncTester() {
    
    
 // 代码部分
}
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

我的答案:
第一种:

function makeFuncTester(array) {
    
    
    return function closureFn(fn) {
    
    
        let res = array.find(item => fn(item[0]) !== item[1]) || []
        return res.length?false:true
    }
}

这种方法是最开始想到的, 利用find找到一个符合预期的就会返回的特性进行判断。

第二种:

function makeFuncTester(array) {
    
    
    return function closureFn(fn) {
    
    
        return array.every(item => fn(item[0]) === item[1]);
    }
}

这个方法的代码看着更加的简单,主要利用every特性,即每一个值都符合预期就会返回true,否则返回false

第三部分 Asynchronicity

这部分对异步调用进行练习

Q1

编写一个名为everyXsecsForYsecs的函数,该函数将接受三个参数:一个函数func,一个时间间隔和另一个持续时间。everyXsecsForYsecs将在指定的间隔时间里 执行给定的函数,但是会在持续时间到达后自动停止。
例如:

function everyXsecsForYsecs() {
    
    
  // 代码部分
}
function theEnd() {
    
    
  console.log("This is the end!");
}
everyXsecsForYsecs(theEnd, 2, 20);
// should invoke theEnd function every 2 seconds, for 20 seconds): This is the end!

我的答案:

function everyXsecsForYsecs(func,interval,duration) {
    
    
    function clear() {
    
    
        clearInterval(print)
    }
    const print =  setInterval(func, interval*1000);
    setTimeout(clear, duration*1000);
}

这个就是一个很简单但是又很典型的异步调用,使用setTimeout将clearInterval事件放进macroTask中

Q2

编写一个函数delayCounter,该函数接受一个数字(称为“ target”)作为第一个参数,并接受一个毫秒数(称为“ duration”)作为第二个参数,并返回一个函数。

调用返回的函数时,这个函数应将介于1到目标数字(target)之间的所有数字输出到控制台,它们之间的间隔为“duration”毫秒。

例如:

function delayCounter() {
    
    
 // 代码部分
}

const countLogger = delayCounter(3, 1000);
countLogger();
//After 1 second, log 1
//After 2 seconds, log 2
//After 3 seconds, log 3

我的答案:

function delayCounter(target,duration) {
    
    
    return function count() {
    
    
      let num = 1
      const print = setInterval(()=>{
    
    
         if(num === target) {
    
    
            clearInterval(print)
        }
        console.log(num)
        num ++
    }, duration);
    }
}

这个 例子锻炼两个部分:1.进行异步调用的训练; 2.复习来上一部分的闭包调用

Q3

编写一个Promise的函数,它接受一个值,并将在2秒后返回这个值。

例如:

function promised() {
    
    
  // 代码部分
}

const createPromise = promised("wait for it...");
createPromise.then((val) => console.log(val));
// will log "wait for it..." to the console after 2 seconds

我的答案:

function promised(val) {
    
    
    return new Promise(resolve => {
    
    
      setTimeout(() => resolve(val), 2000);
    });
  }

这个例子前提需要我们了解什么是promise,了解resolve,reject等,并且练习promise.thensetTimeout如何进行搭配使用,实现将异步操作转为同步操作。代码很短,需要的知识有很多。

Q4

// 懒得翻译了,就上英文吧

Write a SecondClock class, with two methods: start and reset.​
start: upon invocation, invokes a callback (this.cb, defined in the constructor) on an argument every second, with the argument to the callback being the current seconds “value”.

In other words, the callback gets invoked every second on the “seconds hand” of the clock. Always start with 1 and don’t utilize the seconds value of the current computer clock time.

The first “tick” with value 1 occurs 1 second after the initial “secondClock” invocation.
The second “tick” with value 2 occurs 2 seconds after the initial “secondClock” invocation.

The sixtieth “tick” with value 60 occurs 60 seconds after the initial “secondClock” invocation.
The sixty-first “tick” with value 1 occurs 61 seconds after the initial “secondClock” invocation. The sixty-second “tick” with value 2 occurs 62 seconds after the initial “secondClock” invocation.
etc.

reset: upon invocation, completely stops the “clock”. Also resets the time back to the beginning Hint: look up setInterval and clearInterval.

例如:

class SecondClock {
    
    
  // 代码部分 
}

const clock = new SecondClock((val) => {
    
    
  console.log(val);
});
console.log("Started Clock.");
clock.start();
setTimeout(() => {
    
    
  clock.reset();
  console.log("Stopped Clock after 6 seconds.");
}, 6000);

有个注意点,是要用构造函数的

我的答案:

class SecondClock {
    
    
    constructor(cb){
    
    
        this.cb = cb 
        this.time = 0
    }
    start(){
    
    
        this.clock = setInterval(() => {
    
    
            this.cb(this.time % 60)
            this.time++
        }, 1000);
    }
    reset(){
    
    
        clearInterval(this.clock)
        this.time = 0

    }
}

这个 例子我觉得很有趣:

  1. 使用class来写,这个需要我们对构造函数有个基础了解;
  2. 在这里有两个函数功能,分别进行定时循环和清楚定时循环
  3. 整个结果输出的顺序也能看出来事件循环(eventLoop)机制

第四部分 Prototype & Class

Q1

构建一个名为personFromPersonStore的函数,这个函数接收name和age两个参数,当调用这个函数时可以输出,此外在调用personFromPersonStore的函数时,还可以使用personStore中的greet方法,这部分功能在代码部分1中实现。此外,在代码部分2中添加一个introduce函数,这个函数能够输出personFromPersonStore中name的参数。
例如:

// 4 object.create()用法
const personStore = {
    
    
    greet() {
    
    
        console.log('hello')
    }
  };

  function personFromPersonStore(name, age) {
    
    
    // 代码部分1
  }
  
 // 代码部分2

  const sandra = personFromPersonStore("Sandra", 26);
  console.log(sandra.name);
  // -> Logs 'Sandra'
  console.log(sandra.age);
  //-> Logs 26
  sandra.greet();
  //-> Logs 'hello'
  sandra.introduce();
  //-> Logs 'Hi, my name is Sandra'

我的答案:

const personStore = {
    
    
    greet() {
    
    
        console.log('greet')
    }
  };

  function personFromPersonStore(name, age) {
    
    
   const person = Object.create(personStore)
   person.name = name
   person.age = age
   return person
  }
  
  personStore.introduce = function() {
    
    
      console.log(`Hi, my name is ${
      
      this.name}`)
  }

思考:
这个练习了两部分:

  1. 使用了Object.create()的方法,使用这个方法首先是创建一个空对象,然后这个空对象指向person,当我们使用person属性时,如果person本身没有,他就会通过原型链查看personStore是否有这个属性。
    就像作者在说明里面提及的:

Every object has a hidden [[prototype]] property (bad name!). Simply put, when you call a property on an object, the JS engine first checks to see if the object has that property. If it can’t find such a property, it will look at its [[prototype]] property to see which Object it descends from. const person = Object.create(personStore) tells the JS engine to create a new empty object and return it and call it person, but if we call a property of person and person doesn’t have that property, look up to personStore and see if it has that property.

  1. 在添加方法过程中,因为personStore本身就是一个对象,可以在对象上直接添加一个方法。但是提及这点是为了和下面的例子做对比。

Q2

构建一个名为personFromConstructor的函数,这个函数接收name和age两个参数,当调用这个函数时可以输出,此外在调用personFromConstructor的函数时,还可以使用PersonConstructor中的greet方法,这部分功能在代码部分1中实现。此外,在代码部分2中添加一个introduce函数,这个函数能够输出personFromConstructor中name的参数。

例如:

function PersonConstructor() {
    
    
  this.greet = function() {
    
    
      console.log('hello')
  }
}

function personFromConstructor(name, age) {
    
    
  // 代码部分1
}

// 代码部分2
const mike = personFromConstructor("Mike", 30);
console.log(mike.name); // -> Logs 'Mike'
console.log(mike.age); //-> Logs 30
mike.greet(); //-> Logs 'hello
mike.introduce();//-> Logs 'I am Mike'

我的答案:

function PersonConstructor() {
    
    
  this.greet = function() {
    
    
      console.log('hello')
  }
}

function personFromConstructor(name, age) {
    
    
  const person = new PersonConstructor()
  person.name = name
  person.age = age
  return person
}

PersonConstructor.prototype.introduce = function() {
    
    
  console.log(`I am ${
      
      this.name}`)
}

我的思考:

  1. 这里面使用了new的关键字,这里说明一下new一个实例会经历的过程 :
    a. 创建一个新对象:const person = new Object();
    b. 继承对象的原型:person._proto_ = PersonConstructor.prototype;
    c. 将PersonConstructor中this指向person;
    d. 判断PersonConstructor的返回值类型,如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象
    if (typeof(result) == "object") person = result; else person = obj;

  2. 这个例子和上面的例子区别就在于Q1中personStore是一个对象,但是Q2中的PersonConstructor是一个函数,我们不能像常规对象那样直接给函数添加属性,这样添加的属性无法被new的新实例进行使用,所以需要prototype,把这个方法添加到prototype上,这样才可以进行调用 。

结尾语

到这为止,这篇文章也结束了。我将上面链接里文章中每个例子都进行了练习,并且把我练习中的心得和犯过的错误进行了整理,放在了这篇文章中,虽然不知道有多少人能看到这篇文章,又会有多少人能一起练习到最后,不过就我来说,我觉得我个人还是得到了很多的成长,对很多一知半解的知识又进行了一个梳理,对一些不擅长的内容进行了很好的强化训练,现在已成为我开发中的利器了。

最后希望每一个努力的孩子都能够梦想成真~~

猜你喜欢

转载自blog.csdn.net/baidu_33438652/article/details/108552699