1. 精华

2. 语法

1. 若数字字面量有指数部分,该字面量的值等于e之前的数字与10的e之后的数字的次方相乘,例如1e2(1 * 10 ^ 2) === 100

2. 字符串是不可变的
  1. let str = 'javascript'
  2. // str[0] == 'j'
  3. str[0] = 't' // 不生效
  4. str = 'helloWorld' // 可生效,但这里在栈内存开辟了新地址,不是改变旧地址中的值

3. 被判定为false的值(其余均为true)
  • false
  • null
  • undefined
  • 空字符串’’
  • 数字0
  • 数字NaN

4. try、catch、throw和finally
  1. // 栗子1:若throw在try中,执行后控制流跳转到对应catch中
  2. try {
  3. throw (...) // 抛出的异常。可以是字符串、数字、逻辑值或对象。
  4. } catch(err) {
  5. // 通常异常包含一个name属性和一个message属性
  6. } finally {
  7. // 无论结果如何均会执行
  8. }
  9. // 栗子2:若throw在函数中,则函数将中止不会继续执行,控制流跳转至调用该函数的try语句对应的catch中
  10. function UserException(message) {
  11. this.message = message;
  12. this.name = "UserException";
  13. }
  14. function getMonthName(mo) {
  15. mo = mo-1; // 调整月份数字到数组索引 (1=Jan, 12=Dec)
  16. var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
  17. "Aug", "Sep", "Oct", "Nov", "Dec"];
  18. if (months[mo] !== undefined) {
  19. return months[mo];
  20. } else {
  21. throw new UserException("InvalidMonthNo");
  22. console.log('haha') // 这里不执行
  23. }
  24. }
  25. try {
  26. var myMonth = 15; // 15 超出边界并引发异常
  27. var monthName = getMonthName(myMonth);
  28. console.log('lala') // 这里不执行
  29. } catch (e) {
  30. console.log(e)
  31. }

3. 对象

1. 指定某对象作为原型
  1. // 给Object增加一个create方法
  2. Object.create = funciton (obj) {
  3. var F = function () {}
  4. F.prototype = obj
  5. return new F()
  6. }
  7. var newObj = Object.create(oldObj)

2. 原型连接在对象更新时不起作用,检索时才会起作用
  1. // 更新
  2. obj.name = 'ml' // 不会改变obj原型的属性
  3. // 检索
  4. console.log(obj.name) // 若obj中无name属性,会试着在其原型中获取

4. 函数

1. prototype_proto_关系
  1. // _proto_指向的就是他的构造函数的 prototype
  2. function B(name) {
  3. this.name = name;
  4. }
  5. var b = new B("ml");
  6. // b._proto_ == B.prototype

2. 什么是原型链
  1. function B(name) {
  2. this.name = name;
  3. }
  4. var b = new B("ml");
  5. // 在该例子中,若调用 b.toString(),由于其本身没有该方法,会在其 _proto_(即构造函数的 prototype)中寻找
  6. // 最后会在Object.prototype中找到 toString()

3. this指向问题
  1. // 函数: 谁调用了我,我里边儿的 this指向谁
  2. // 以函数调用模式(函数形式)调用的函数,this指向全局
  3. function add(a, b) {
  4. return a + b
  5. }
  6. let myObj = {
  7. value: 1,
  8. double: function () {
  9. let that = this // 若不采用该解决方法,会导致 helper中执行异常,因为 this指向了 window,而不是 myObj
  10. let helper = function () {
  11. that.value = add(that.value, that.value)
  12. }
  13. helper() // 以函数形式调用 this指向全局 window
  14. }
  15. }
  16. myObj.double() // 以方法形式调用,this指向 myObj

4. call、apply、bind你们是干嘛的
  1. 三者用于改变函数的this指向
  2. 比如:b.call(a);
  3. // b的 this指向了 a(即 b的 this上下文被改变了,变 a的了,太惨了)
  4. // 补充1:一般多数情况 b为函数, a为对象,调用的是 b函数
  5. // 补充2:以下为 new函数的实现,其中的 o对象在调用 apply后会改变
  6. function Person(first,last) {
  7. this.first = first;
  8. this.last = last;
  9. }
  10. function trivialNew(constructor, ...args) {
  11. var o = {}; // 创建一个对象
  12. constructor.apply(o, args);
  13. return o;
  14. }
  15. let bill = trivialNew(Person, "William", "Orange");// 这里即相当于 new函数
  1. // 栗子1(直接理解)
  2. let a = {
  3. name: 'gulululala', //定义a的属性
  4. say: function() { //定义a的方法
  5. console.log("你好啊!");
  6. }
  7. };
  8. function b(name){
  9. console.log("我原来叫:"+ name);
  10. console.log("我现在叫:"+ this.name);
  11. this.say();
  12. }
  13. b.call(a,'test');
  14. //我原来叫:test
  15. //我现在叫:gulululala
  16. //你好啊!
  1. // 栗子2(后者想用前者的方法)
  2. function Person(name) {
  3. this.name = name;
  4. }
  5. Person.prototype.showName = function () {
  6. console.log(this.name);
  7. };
  8. let person = new Person('malie');
  9. person.showName();
  10. let animal = {
  11. name: 'cat'
  12. };
  13. // 若新定义的 animal对象也想用 person中的 showName方法,有以下三种方法
  14. // 1 call用法
  15. person.showName.call(animal);
  16. // 2 apply用法
  17. person.showName.apply(animal);
  18. // 3 bind用法
  19. person.showName.bind(animal)();
  20. // 用法总结:前两个改变 obj的 this上下文后直接执行,第三个是返回了改变了上下文后的函数
  21. // call与apply用法区别在于apply传的是参数数组
  22. // bind调用参数格式与call相同(逐个调用)
  23. fn.call(obj, arg1, arg2, arg3...);
  24. fn.apply(obj, [arg1, arg2, arg3...]);

5. 闭包栗子
  1. // bad
  2. // 使用这种方式给某个 dom节点组设置点击事件,结果预想是节点的序号
  3. // 但结果是,每个节点总显示的是节点组的总数目
  4. var add_the_handlers = function (nodes) {
  5. for (var i = 0; i < nodes.length; i += 1) {
  6. nodes[i].onclick = function () {
  7. alert(i)
  8. }
  9. }
  10. }
  11. // add_the_handlers(document.getElementsByTagName('li'))

产生这种结果的原因是事件处理器函数绑定的是变量i,而不是函数在构造时i的值。或者可以说,对于事件处理函数而言,其内部上下文中并不存在变量i。点击事件执行时,会尝试沿作用域链向父级的作用域中去寻找。而在上一级,也即add_the_handlers这个函数的上下文中,找到了变量i,经循环后i即为nodes的长度

  1. // good
  2. // 使用闭包
  3. var add_the_handlers = function (nodes) {
  4. for (var i = 0; i < nodes.length; i += 1) {
  5. nodes[i].onclick = function (e) {
  6. return function() {
  7. alert(e)
  8. }
  9. }(i)
  10. }
  11. }

使用闭包可达到预想的效果原因是,事件处理函数绑定的是传递进去的i的值,而不是add_the_handlersi的值

  1. // very good
  2. // ES6可用let实现
  3. var add_the_handlers = function (nodes) {
  4. for (let i = 0; i < nodes.length; i += 1) {
  5. nodes[i].onclick = function () {
  6. alert(i)
  7. }
  8. }
  9. }

6. 使用“记忆”优化算法

函数可以使用对象去记住先前操作的结果,从而能避免无谓的运算,这种优化被称为记忆(避免重复演算之前已被处理的输入)

举个栗子:递归计算 Fibonacci数列

  1. let count = 0 // 仅用于计数
  2. let fibonacci = function (n) {
  3. count++ // 仅用于计数
  4. return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
  5. }
  6. for(let i = 0; i <= 10; i += 1) {
  7. console.log(i + ':' + fibonacci(i))
  8. }
  9. console.log(count) // 453
  10. // 0:0
  11. // 1:1
  12. // 2:1
  13. // 3:2
  14. // 4:3
  15. // 5:5
  16. // 6:8
  17. // 7:13
  18. // 8:21
  19. // 9:34
  20. // 10:55
  21. // 453

以上栗子,我们循环调用了11次,但fibonacci函数被调用了453次,其中大多数均在计算先前已处理过的输入

  1. let count = 0 // 仅用于计数
  2. let fibonacci = function () {
  3. let memo = [0, 1]
  4. let fib = function (n) {
  5. count++ // 仅用于计数
  6. let result = memo[n]
  7. if (typeof result !== 'number') {
  8. result = fib(n - 1) + fib(n - 2)
  9. memo[n] = result
  10. }
  11. return result
  12. }
  13. return fib
  14. }()
  15. for(let i = 0; i <= 10; i += 1) {
  16. console.log(i + ':' + fibonacci(i)) // 结果同上
  17. }
  18. console.log(count) // 29

优化后的方法,将结果隐藏在闭包中,使用“记忆”的方式记录先前处理过的输入,最后调用29次函数即可或者结果