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 = obj
return new F()
}
var newObj = Object.create(oldObj)
2. 原型连接在对象更新时不起作用,检索时才会起作用
// 更新
obj.name = 'ml' // 不会改变obj原型的属性
// 检索
console.log(obj.name) // 若obj中无name属性,会试着在其原型中获取
4. 函数
1. prototype
与_proto_
关系
// _proto_指向的就是他的构造函数的 prototype
function 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,而不是 myObj
let 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次函数即可或者结果