1. 精华
2. 语法
1. 若数字字面量有指数部分,该字面量的值等于e之前的数字与10的e之后的数字的次方相乘,例如1e2(1 * 10 ^ 2) === 100
2. 字符串是不可变的
let str = 'javascript'// str[0] == 'j'str[0] = 't' // 不生效str = 'helloWorld' // 可生效,但这里在栈内存开辟了新地址,不是改变旧地址中的值
3. 被判定为false的值(其余均为true)
- false
- null
- undefined
- 空字符串’’
- 数字0
- 数字NaN
4. try、catch、throw和finally
// 栗子1:若throw在try中,执行后控制流跳转到对应catch中try {throw (...) // 抛出的异常。可以是字符串、数字、逻辑值或对象。} catch(err) {// 通常异常包含一个name属性和一个message属性} finally {// 无论结果如何均会执行}// 栗子2:若throw在函数中,则函数将中止不会继续执行,控制流跳转至调用该函数的try语句对应的catch中function UserException(message) {this.message = message;this.name = "UserException";}function getMonthName(mo) {mo = mo-1; // 调整月份数字到数组索引 (1=Jan, 12=Dec)var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul","Aug", "Sep", "Oct", "Nov", "Dec"];if (months[mo] !== undefined) {return months[mo];} else {throw new UserException("InvalidMonthNo");console.log('haha') // 这里不执行}}try {var myMonth = 15; // 15 超出边界并引发异常var monthName = getMonthName(myMonth);console.log('lala') // 这里不执行} catch (e) {console.log(e)}
3. 对象
1. 指定某对象作为原型
// 给Object增加一个create方法Object.create = funciton (obj) {var F = function () {}F.prototype = objreturn new F()}var newObj = Object.create(oldObj)
2. 原型连接在对象更新时不起作用,检索时才会起作用
// 更新obj.name = 'ml' // 不会改变obj原型的属性// 检索console.log(obj.name) // 若obj中无name属性,会试着在其原型中获取
4. 函数
1. prototype与_proto_关系
// _proto_指向的就是他的构造函数的 prototypefunction B(name) {this.name = name;}var b = new B("ml");// b._proto_ == B.prototype
2. 什么是原型链
function B(name) {this.name = name;}var b = new B("ml");// 在该例子中,若调用 b.toString(),由于其本身没有该方法,会在其 _proto_(即构造函数的 prototype)中寻找// 最后会在Object.prototype中找到 toString()
3. this指向问题
// 函数: 谁调用了我,我里边儿的 this指向谁// 以函数调用模式(函数形式)调用的函数,this指向全局function add(a, b) {return a + b}let myObj = {value: 1,double: function () {let that = this // 若不采用该解决方法,会导致 helper中执行异常,因为 this指向了 window,而不是 myObjlet helper = function () {that.value = add(that.value, that.value)}helper() // 以函数形式调用 this指向全局 window}}myObj.double() // 以方法形式调用,this指向 myObj
4. call、apply、bind你们是干嘛的
三者用于改变函数的this指向比如:b.call(a);// b的 this指向了 a(即 b的 this上下文被改变了,变 a的了,太惨了)// 补充1:一般多数情况 b为函数, a为对象,调用的是 b函数// 补充2:以下为 new函数的实现,其中的 o对象在调用 apply后会改变function Person(first,last) {this.first = first;this.last = last;}function trivialNew(constructor, ...args) {var o = {}; // 创建一个对象constructor.apply(o, args);return o;}let bill = trivialNew(Person, "William", "Orange");// 这里即相当于 new函数
// 栗子1(直接理解)let a = {name: 'gulululala', //定义a的属性say: function() { //定义a的方法console.log("你好啊!");}};function b(name){console.log("我原来叫:"+ name);console.log("我现在叫:"+ this.name);this.say();}b.call(a,'test');//我原来叫:test//我现在叫:gulululala//你好啊!
// 栗子2(后者想用前者的方法)function Person(name) {this.name = name;}Person.prototype.showName = function () {console.log(this.name);};let person = new Person('malie');person.showName();let animal = {name: 'cat'};// 若新定义的 animal对象也想用 person中的 showName方法,有以下三种方法// 1 call用法person.showName.call(animal);// 2 apply用法person.showName.apply(animal);// 3 bind用法person.showName.bind(animal)();// 用法总结:前两个改变 obj的 this上下文后直接执行,第三个是返回了改变了上下文后的函数// call与apply用法区别在于apply传的是参数数组// bind调用参数格式与call相同(逐个调用)fn.call(obj, arg1, arg2, arg3...);fn.apply(obj, [arg1, arg2, arg3...]);
5. 闭包栗子
// bad// 使用这种方式给某个 dom节点组设置点击事件,结果预想是节点的序号// 但结果是,每个节点总显示的是节点组的总数目var add_the_handlers = function (nodes) {for (var i = 0; i < nodes.length; i += 1) {nodes[i].onclick = function () {alert(i)}}}// add_the_handlers(document.getElementsByTagName('li'))
产生这种结果的原因是事件处理器函数绑定的是变量i,而不是函数在构造时i的值。或者可以说,对于事件处理函数而言,其内部上下文中并不存在变量i。点击事件执行时,会尝试沿作用域链向父级的作用域中去寻找。而在上一级,也即add_the_handlers这个函数的上下文中,找到了变量i,经循环后i即为nodes的长度
// good// 使用闭包var add_the_handlers = function (nodes) {for (var i = 0; i < nodes.length; i += 1) {nodes[i].onclick = function (e) {return function() {alert(e)}}(i)}}
使用闭包可达到预想的效果原因是,事件处理函数绑定的是传递进去的i的值,而不是add_the_handlers中i的值
// very good// ES6可用let实现var add_the_handlers = function (nodes) {for (let i = 0; i < nodes.length; i += 1) {nodes[i].onclick = function () {alert(i)}}}
6. 使用“记忆”优化算法
函数可以使用对象去记住先前操作的结果,从而能避免无谓的运算,这种优化被称为记忆(避免重复演算之前已被处理的输入)
举个栗子:递归计算 Fibonacci数列
let count = 0 // 仅用于计数let fibonacci = function (n) {count++ // 仅用于计数return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)}for(let i = 0; i <= 10; i += 1) {console.log(i + ':' + fibonacci(i))}console.log(count) // 453// 0:0// 1:1// 2:1// 3:2// 4:3// 5:5// 6:8// 7:13// 8:21// 9:34// 10:55// 453
以上栗子,我们循环调用了11次,但fibonacci函数被调用了453次,其中大多数均在计算先前已处理过的输入
let count = 0 // 仅用于计数let fibonacci = function () {let memo = [0, 1]let fib = function (n) {count++ // 仅用于计数let result = memo[n]if (typeof result !== 'number') {result = fib(n - 1) + fib(n - 2)memo[n] = result}return result}return fib}()for(let i = 0; i <= 10; i += 1) {console.log(i + ':' + fibonacci(i)) // 结果同上}console.log(count) // 29
优化后的方法,将结果隐藏在闭包中,使用“记忆”的方式记录先前处理过的输入,最后调用29次函数即可或者结果
