第1章 关于this
1.1 为什么要用this?
对比一下使用this和不使用this的区别?
显式的传递上下文:
function identify(context){
return context.name.toUpperCase()
}
funcion speak(context){
var greeting = "Hello, I'm" + identify(context)
console.log(greeting)
}
var me = {
name: "Kyle"
}
var you = {
name: "Reader"
}
identify(you)
speak(me)
使用this,this提供了一种更优雅的方式来隐式"传递"一个对象引用:
function identify(){
return this.name.toUpperCase()
}
function speak(){
var greeting = "Hello, I'm" + identify.call(this)
console.log(greeting)
}
var me = {
name: "Kyle"
}
var you = {
name: "Reader"
}
identify.call(me)
identify.call(you)
speak.call(me)
speak.call(you)
1.2 误解
1.2.1 指向自身
function foo(num){
console.log("foo:" + num)
this.count++
}
foo.count = 0
var i;
for(i = 0; i < 10; i++){
if(i > 5){
foo(i)
}
}
console.log(foo.count)
上面的🌰输出什么?怎么解决呢?
- 使用foo标识符代替this来引用函数对象。
- 强制this指向foo函数对象。
1.2.2 它的作用域
function foo(){
var a = 2
this.bar()
}
function bar(){
console.log(this.a)
}
foo()
上面的🌰输出什么?
this实际上是在函数被调用时发生的绑定,它指向什么完全取决于---。
第2章 this的全面解析
2.1 调用位置
看看什么是调用栈和调用位置:
function baz(){
console.log("baz")
bar()
}
function bar(){
console.log("bar")
foo()
}
function foo(){
console.log("foo")
}
baz()
2.2 绑定规则
2.2.1 默认绑定
function foo(){
console.log(this.a)
}
var a = 2
foo()
非严格模式下,上面的🌰输出什么?
function foo(){
"use strict"
console.log(this.a)
}
var a = 2
foo()
上面的🌰输出什么?
function foo(){
console.log(this.a)
}
var a = 2
(function(){
'use strict'
foo()
})()
上面的🌰输出什么?
2.2.2 隐式绑定
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo()
上面的🌰输出什么?
function foo(){
console.log(this.a)
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo()
上面的🌰输出什么?
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo
var a = "oops, global"
bar()
上面的🌰输出什么?
function foo(){
console.log(this.a)
}
function doFoo(fn){
fn()
}
var obj = {
a: 2,
foo: foo
}
var a = "oops, global"
doFoo(obj.foo)
上面的🌰输出什么?
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var a = "oops, global"
setTimeout(obj.foo, 100)
上面的🌰输出什么?
2.2.3 显式绑定
function foo(){
console.log(this.a)
}
var obj = {
a: 2
}
foo.call(obj)
1.硬绑定
function foo(){
console.log(this.a)
}
var obj = {
a: 2
}
var bar = function(){
foo.call(obj)
}
bar()
setTimeout(bar, 100)
bar.call(window)
上面的🌰输出什么?
function foo(something){
console.log(this.a, something)
return this.a + something
}
var obj = {
a: 2
}
var bar = function(){
return foo.apply(obj, arguments)
}
var b = bar(3)
console.log(b)
上面的🌰输出什么?
function foo(something){
console.log(this.a, something)
return this.a + something
}
function bind(fn, obj){
return function(){
return fn.apply(obj, arguments)
}
}
var obj = {
a: 2
}
var bar = bind(foo, obj)
var b = bar(3)
console.log(b)
上面的🌰输出什么?
function foo(something){
console.log(this.a, something)
return this.a + something
}
var obj = {
a: 2
}
var bar = foo.bind(obj)
var b = bar(3)
console.log(b)
上面的🌰输出什么?
2. API 调用的"上下文"
function foo(el){
console.log(el, this.id)
}
var obj = {
id: "awesome"
}
[1,2,3].forEach(foo, obj)
上面的🌰输出什么?
2.2.4 new绑定
2.3 优先级
function foo(){
console.log(this.a)
}
var obj1 = {
a: 2,
foo: foo
}
var obj2 = {
a: 3,
foo: foo
}
obj1.foo()
obj2.foo()
obj1.foo.call(obj2)
obj2.foo.call(obj1)
上面的🌰输出什么?
隐式绑定和显式绑定哪个优先级更高?
function foo(something){
this.a = something
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(2)
console.log(obj1.a)
obj1.foo.call(obj2, 3)
console.log(obj2.a)
var bar = new obj1.foo(4)
console.log(obj1.a)
console.log(bar.a)
上面的🌰输出什么?
new绑定和隐式绑定哪个优先级更高?
function foo(something){
this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)
var baz = new bar(3)
console.log(obj1.a)
console.log(baz.a)
上面的🌰输出什么?
new绑定和硬绑定哪个优先级更高?
bind(...)的功能之一就是可以把第一个参数之外的所有参数传给下层函数(是"柯里化"的一种)。
function foo(p1, p2){
this.val = p1 + p2
}
var bar = foo.bind(null, "p1")
var baz = new bar("p2")
baz.val
上面的🌰输出什么?
2.4 绑定例外
2.4.1 被忽略的this
🌟
function foo(){
console.log(this.a)
}
var a = 2
foo.call(null)
上面的🌰输出什么?
function foo(a, b){
console.log("a:"+ a + ",b:" + b)
}
foo.apply(null, [2, 3])
var bar = foo.bind(null, 2)
bar(3)
上面的🌰输出什么?
2.4.2 间接引用
间接引用最容易在赋值时发生:
function foo(){
console.log(this.a)
}
var a = 2
var o = {a: 3, foo: foo}
var p = {a: 4}
o.foo()
(p.foo = o.foo)()
2.4.3 软绑定
使用硬绑定之后就无法使用隐式绑定或显式绑定来修改this。
软绑定:如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定一样的效果,同时保留隐式绑定或显式绑定修改this的能力。
function foo() {
console.log("name:" + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind(obj);
fooOBJ();
obj2.foo = foo.softBind(obj);
obj2.foo();
fooOBJ.call(obj3);
setTimeout(obj2.foo, 10);
软绑定还理解不了。
🌟2.5 this词法
箭头函数:根据外层(函数或者全局)作用域来决定this。
function foo(){
return (a) => {
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1)
bar.call(obj2)
上面的🌰输出什么?
function foo(){
setTimeout(() => {
console.log(this.a)
}, 100)
}
var obj = {
a: 2
}
foo.call(obj)
上面的🌰输出什么?
function foo(){
var self = this;
setTimeout(function(){
console.log(self.a)
}, 100)
}
var obj = {
a: 2
}
foo.call(obj)
上面的🌰输出什么?
第3章 对象
3.1 语法
对象的文字语法:
var myObj = {
key: value
// ...
}
构造形式大概是:
var myObj = new Object()
myObj.key = value
3.2 类型
六种简单基本类型?
typeof null 返回什么?
3.3 内容
属性访问:
var myObject = {
a: 2
}
myObject.a
myObject["a"]
3.3.1 可计算属性名
var prefix = "foo"
var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
}
myObject["foobar"]
myObject["foobaz"]
3.3.2 属性与方法
无论返回值是什么类型,每次访问对象的属性就是属性访问。
function foo(){
console.log("foo")
}
var someFoo = foo
var myObject = {
someFoo: foo
}
foo;
someFoo;
myObject.someFoo
3.3.3 数组
var myArray = ["foo", 42, "bar"]
myArray.length;
myArray[0]
myArray[2]
上面的🌰输出什么?
var myArray = ["foo", 42, "bar"]
myArray.baz = "baz"
myArray.length
myArray.baz
上面的🌰输出什么?
var myArray = ["foo", 42, "bar"]
myArray["3"] = "baz"
myArray.length;
myArray[3]
上面的🌰输出什么?
3.3.4 复制对象
思考一下这个对象🌰:
function anotherFunction(){/*..*/}
var anotherObject = {
c: true
}
var anotherArray = []
var myOject = {
a: 2,
b: anotherObject,
c: anotherArray,
d: anotherFunction
}
anotherArray.push(anotherObject, myOject)
- 浅复制还是深复制?
- 深复制会有什么问题?怎么解决?
- ES6定义了Object.assign(...)方法是干嘛的?它怎么用?
3.3.5 属性描述符
ES5开始,所有的属性都具备了属性描述符。
var myObject = {
a: 2
}
Object.getOwnPropertyDescriptor(myObject, "a")
上面的🌰输出什么?
var myObject = {}
Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
})
myObject.a
上面的🌰输出什么?
1. Writable
var myObject = {}
Object.defineProperty(myObject, "a", {
value: 2,
writable: false,
configurable: true,
enumerable: true
})
myObject.a = 3
myObject.a
上面的🌰输出什么? 如果是在严格模式下呢?
"use strict"
var myObject = {}
Object.defineProperty(myObject, "a", {
value: 2,
writable: false,
configurable: true,
enumerable: true
})
myObject.a = 3
2. Configurable
属性是可配置的,就可以使用defineProperty(...)方法修改属性描述符:
var myObject = {
a: 2
}
myObject.a = 3
myObject.a
Object.defineProperty(myObject, "a", {
value: 4,
writable: true,
configurable: false
})
myObject.a
myObject.a = 5
myObject.a
Object.defineProperty(myObject, "a", {
value: 6,
writable: true,
configurable: true,
enumerable: true
})
上面的🌰输出什么?
var myObject = {
a: 2
}
myObject.a
delete myObject.a
myObject.a
Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
configurable: false,
enumerable: true
})
myObject.a
delete myObject.a
myObject.a
上面的🌰输出什么?
3. Enumerable
这个描述符怎么用?
3.3.6 不变性
1.对象常量
var myObject = {}
Object.definePropery(myObject, "FAVORITE NUMBER", {
value: 42,
writable: false,
configurable: false
})
2.禁止扩展
var myObject = {
a: 2
}
Object.preventExtensions(myObject)
myObject.b = 3
myObject.b
上面的🌰输出什么?
3.密封
密封之后会怎么样?
4.冻结
冻结之后会怎么样?
3.3.7 [[Get]]
var myObject = {
a: 2
}
myObject.a
上面的🌰输出什么?
var myObject = {
a: 2
}
myObject.b
上面的🌰输出什么?
如果是引用了一个当前词法作用域中不存在的变量,会输出什么?
3.3.8 [[Put]]
[[Put]]操作的步骤?
3.3.9 Getter和Setter
var myObject = {
get a(){
return 2
}
}
Object.defineProperty(
myObject,
"b",
{
get: function(){ return this.a * 2},
enumerable: true
}
)
myObject.a;
myObject.b;
上面的🌰输出什么?
var myObject = {
get a(){
return 2
}
}
myObject.a = 3
myObject.a
上面的🌰输出什么?
var myObject = {
get a(){
return this.a
},
set a(val){
this.a = val * 2
}
}
myObject.a = 2
myObject.a
上面的🌰输出什么?
3.3.10 存在性
var myObject = {
a: 2
}
("a" in myObject)
("b" in myObject)
myObject.hasOwnProperty("a")
myObject.hasOwnProperty("b")
上面的🌰输出什么?
in操作符和hasOwnProperty(...)有什么区别?
4 in [2, 4, 6]
上面的🌰输出什么?
1.枚举
var myObject = {}
Object.defineProperty(
myObject,
"a",
{
enumberable: true,
value: 2
}
)
Object.defineProperty(
myObject,
"b",
{
enumberable: false,
value: 3
}
)
myObject.b;
("b" in myObject);
myObject.hasOwnProperty("b");
//.......
for(var k in myObject){
console.log(k, myObject[k])
}
上面的🌰输出什么?
for...in可以用来遍历数组吗?
var myObject = {}
Object.defineProperty(
myObject,
"a",
{
enumberable: true,
value: 2
}
)
Object.defineProperty(
myObject,
"b",
{
enumberable: false,
value: 3
}
)
myObject.propertyIsEnumertable("a")
myObject.propertyIsEnumertable("b")
Object.keys(myObject)
Object.getOwnPropertyNames(myObject)
上面的🌰输出什么?
Object.keys(...)和Object.getOwnPropertyNames(...)有什么区别?
遍历
var myArray = [1,2,3]
for(var i = 0; i < myArray.length; i++){
console.log(myArray[i])
}
上面的🌰输出什么?
var myArray = [1, 2, 3]
for(var v of myArray){
console.log(v)
}
上面的🌰输出什么?
var myArray = [1,2,3]
var it = myArray[Symobol.iterator]()
it.next();
it.next();
it.next();
it.next();
上面的🌰输出什么?
如何为想遍历的对象定义@@iterator?
var myObject = {
a: 2,
b: 3
}
// 实现@@iterator
var it = myObject[Symbol.iterator]()
it.next()
it.next()
it.next()
如何定义一个"无限"迭代器?它永远不会"结束"并且总会返回一个新值。
var random =
// 实现random迭代器
var random pool = []
for(var n of randoms){
random pool.push(n)
if(randoms pool.length === 100) break;
}
第4章
4.1 类理论
JavaScript的机制其实和类完全不同。
4.2 类的机制
4.2.1 建造
"类"和"实例"的概念来源于房屋建造。(好新奇,第一个听说这个比喻!一下子就通透了)。
建筑师只会去规划一个建筑蓝图。建筑工人会按照蓝图建造建筑。
一个类就是一张蓝图。为了获得真正可以交互的对象,我们必须按照类来建造(也可以说实例化)一个东西。
类通过复制操作被实例化为对象形式:
4.2.2 构造函数
class CoolGuy{
specialTrick = nothing
CoolGuy(trick){
specialTrick = trick
}
showOff(){
output("Here's my trick:", specialTrick)
}
}
调用类构造函数来生成一个CoolGuy实例:
Joe = new CoolGuy("jumping rope")
Joe.showOff() // Here's my trick: jumping rope
4.3 类的继承
class Vehicle {
engines = 1
ignition(){
output("Turning on my engine.")
}
drive(){
ignition()
output("Steerubg and moving forward! ")
}
}
class Car inherits Vehicle {
wheels = 4
drive(){
inherited: drive()
output("Rolling on all", wheels, "wheels!")
}
}
class SpeedBoat inherits Vehicle{
engines = 2
ignition(){
output("Turning on my", engines, " engines.")
}
pilot(){
inherited: drive()
output("Speeding through the water with ease! ")
}
}
4.3.1 多态
什么是多态?
4.3.2 多重继承
子类D继承自两个父类(B和C),这两个父类都继承自A。如果A中有drive()方法并且B和C都重写了这个方法(多态),那当D引用drive()时应当选择哪个版本呢(B: drive()还是C:drive())?
4.4 混入
4.4.1 显式混入
// 实现mixin方法
var Vehicle = {
engines: 1,
ignition: function(){
console.log("Turning on my engine.")
},
drive: function(){
this.ignition()
console.log("Steerging and moving forward!")
}
}
var Car = mixin(Vehicle, {
wheeks: 4,
drive: function(){
Vehicle.drive.call(this)
console.log("Rolling on all " + this.wheels + "wheels! ")
}
})
在JavaScript中不存在类,Vehicle和Car都是对象,供我们分别进行复制和粘贴。
1. 再说多态
Vehicle.drive.call(this)就是显式多态。
2. 混合复制
// 另一种混入函数,可能有重写风险
var Vehicle = {
// ...
}
// 首先创建一个空对象并把Vehicle的内容复制进去
// 然后把新内容复制到Car中
Car就和Vehicle分离了,向Car中添加属性不会影响Vehicle。
3. 寄生继承
// "传统的JavaScript类"Vehicle
function Vehicle(){
this.engines = 1
}
Vehicle.prototype.ignition = function(){
console.log("Turning on my engine.")
}
Vehicle.prototype.drive = function(){
this.ignition()
console.log("Steering and moving forward!")
}
// 完成”寄生类“Car
function Car(){
}
var myCar = new Car()
myCar.drive()
4.4.2 隐式混入
var Something = {
cool: function(){
this.greeting = "Hello World"
this.count = this.count ? this.count + 1 : 1
}
}
Something.cool();
Something.greeting;
Something.count;
var Another = {
cool: function(){
// 隐式把Something混入Another
Something.cool.call(this)
}
}
Another.cool();
Another.greeting;
Another.count;
上面的🌰输出什么?
第5章 原型
5.1 [[Pototype]]
var myObject = {
a: 2
}
myObject.a;
对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会访问对象的[[Prototype]]链。
var anotherObject = {
a: 2
}
var myObject = Object.create(anotherObject)
myObject.a;
Object.create是做什么的?上面的🌰输出什么?
var anotherObject = {
a: 2
}
var myObject = Object.create(anotherObject)
for(var k in myObject){
console.log("found: " + k)
}
("a" in myObject);
上面的🌰输出什么?
5.1.2 属性设置和屏蔽
myObject.foo = "bar"
- 如果myObject对象中包含foo,这条赋值语句会怎么样?
- 如果foo不是直接存在于myObject中,这条赋值语句会怎么样?
- 如果foo既出现在myObject中也出现在myObject的
[[Prototype]]链上层,会发生什么?
屏蔽的情况更加复杂:
- 如果foo在
[[Prototype]]链上层,并且没有被标记为只读,会发生什么? - 如果foo在
[[Prototype]]链上层,并有被标记为只读,会发生什么? - 如果foo在
[[Prototype]]链上层,并且它是一个setter,会发生什么?
如果你希望第二种和第三种情况下也屏蔽foo,那就不能使用=,而是使用什么呢?
var anotherObject = {
a: 2
}
var myObject = Object.create(anotherObject)
anotherObject.a;
myObject.a;
anotherObject.hasOwnProperty("a")
myObject.hasOwnProperty("a")
myObject.a++;
anotherObject.a;
myObject.a;
myObject.hasOwnProperty("a")
上面的🌰输出什么?
5.2 "类"
5.2.1 "类"函数
function Foo(){
// ...
}
var a = new Foo()
Object.getPrototypeOf(a) === Foo.prototype;
上面的🌰输出什么?
继承意味着复制操作,JS(默认)并不会复制对象属性。相反,JS会在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数。
5.2.2 "构造函数"
function Foo(){
// ...
}
Foo.prototype.constructor === Foo;
var a = new Foo();
a.constructor === Foo;
new会劫持所有普通函数并用构造对象的形式来调用它。
function NothingSpecial(){
console.log("Don't mind me! ")
}
var a = new NothingSpecial();
a;
5.2.3 技术
function Foo(name){
this.name = name;
}
Foo.prototype.myName = function(){
return this.name;
}
var a = new Foo("a")
var b = new Foo("b")
a.myName()
b.myName()
上面的🌰输出什么?
function Foo(){/*...*/}
Foo.prototype = {/*...*/}
var a1 = new Foo();
a1.constructor === Foo;
a1.constructor === Object;
上面的🌰输出什么?constructor能表示被由...构造吗?
5.3 (原型) 继承
function Foo(name){
this.name = name
}
Foo.prototype.myName = function(){
return this.name
}
function Bar(name, label){
Foo.call(this, name)
this.label = label
}
// 我们创建了一个新的Bar.prototype对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.myLabel = function(){
return this.label
}
var a = new Bar("a", "obj a")
a.myName();
a.myLabel();
上面的🌰输出什么?
Bar.prototype = Foo.prototype;
假如用上面这种方式关联对象有什么问题?
Bar.prototype = new Foo()
假如用上面这种方式关联对象又有什么问题?
检查"类"关系
function Foo(){
// ...
}
Foo.prototype.blah = ...;
var a = new Foo();
有哪三种方式能找出a的"祖先"?
5.4 对象关联
5.4.1 创建关联
var foo = {
something: function(){
console.log("Tell me something good...")
}
}
var bar = Object.create(foo);
bar.something();
上面的🌰输出什么?
如何实现Object.create(...)的功能?
var anotherObject = {
a: 2
}
var myObject = Object.create(anotherObject, {
b: {
enumerable: false,
writable: true,
configurable: false,
value: 3
},
c: {
enumerable: true,
writable: false,
configurable: false,
value: 4
}
})
myObject.hasOwnProperty("a");
myObject.hasOwnProperty("b");
myObject.hasOwnProperty("c");
myObject.a;
myObject.b;
myObject.c;
上面的🌰输出什么?
5.4.2 关联关系是备用
var anotherObject = {
cool: function(){
console.log("cool! ")
}
}
var myObject = Object.create(anotherObject);
myObject.coo();
上面的🌰输出什么?
第6章 行为委托
原型链是什么?
6.1 面向委托的设计
6.1.1 类理论
首先定义一个通用父(基)类,可以将其命名为Task,在Task类中定义所有任务都有的行为。接着定义子类XYZ和ABC,它们都继承自Task并会添加一些特殊的行为来处理对应的任务。
class Task {
id;
// 构造函数Task()
Task(ID) { id = ID; }
outoutTask() { output(id); }
}
class XYZ inherits Task {
label;
// 构造函数XYZ()
XYZ(ID, Label) {
super(ID);
lable = Label;
}
outputTask() {
super();
output(label);
}
}
class ABC inherits Task{
// ...
}
6.1.2 委托理论
现在试着使用委托行为而不是类来思考同样的问题。
委托行为意味着某些对象(XYZ)在找不到属性或者方式引用时会把这个请求委托给另一个对象(Task)。
委托行为和类有什么区别呢?
function Foo(){}
var a1 = new Foo();
a1;
上面🌰在Chrome开发者工具的控制台中结果是什么?上面🌰在Firefox中结果又是什么?
function Foo(){}
var a1 = new Foo();
a1.constructor;
a1.constructor.name;
上面的🌰在Chrome中输出什么?
function Foo(){}
var a1 = new Foo();
Foo.prototype.constructor = function Gotcha(){}
a1.constructor;
a1.constructor.name;
a1;
上面的🌰在Chrome中输出什么?
var Foo = {};
var a1 = Object.create(Foo);
a1;
Object.defineProperty(Foo, "constructor", {
enumerable: false,
value: function Gotcha(){}
})
a1;
上面的🌰在Chrome中输出什么?
6.1.3 比较思维模型
下面是典型的("原型")面向对象风格:
function Foo(who){
this.me = who;
}
Foo.prototype.identify = function(){
return "I am " + this.me;
}
function Bar(who){
Foo.call(this, who)
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function(){
alert("Hello, "+this.identify()+".")
}
var b1 = new Bar("b1")
var b2 = new Bar("b2")
b1.speak();
b2.speak();
下面使用对象关联的风格来编写功能完全相同的代码:
Foo = {
init: function(who){
this.me = who;
},
identify: function(){
return "I am" + this.me;
}
}
Bar = Object.create(Foo)
Bar.speak = function(){
alert("Hello, " + this.identify()+".")
}
var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2")
b1.speak();
b2.speak();
对比一下这两种方式。