JavaScript: The Hard Part of Object Oriented JavaScript
Introduction
- The 5 capacities we look for in candidates
我们在候选人身上寻找的5种能力
- Analytical problem solving with code
用代码分析解决问题的能力
- Technical communication (can I implement your approach just from your explanation)
技术沟通能力(我能仅通过你的描述实现你的方法吗)
- Engineering best practices and approach (Debugging, code structure, patience and reference to documentation)
编程的最佳实践和方法(Debugging,代码结构,耐心和文档)
- Non-technical communication (empathetic and thoughtful communication)
非技术性沟通能力(同理心和思考后的交流)
- Language and computer science experience
语言和计算机科学经验
- Analytical problem solving with code
Object Oriented Paradigm
- OOP - an enormously popular paradigm for structuring our complex code
面向对象编程 - 一种非常流行的用于构建复杂代码编程范式
- Easy to add features and functionality
可以简便的添加功能
- Easy for us and other developers to reason about + (a clear structure)
容易理解
- Performant (efficient in terms of memory)
性能表现好(在内存方面的效率)
- Easy to add features and functionality
Object Creation
Creating an Object
- Let’s suppose we’re building a quiz game with users
- Some of our users
- Name: Phil
- Score: 4
- Name: Julia
- Score: 5
- Functionality
- Ability to increase score
- What would be the best way to store this data and functionality?
- Some of our users
1 | const user1 = { |
This is the principle of encapsulation. Let’s keep creating our objects
这就是封装的原理。让我们继续创建对象。
- Note we would in reality have a lot of different relevant functionality for our user objects
- Ability to increase score
- Ability to decrease score
- Delete user
- Log in user
- Log out user
- Add avatar
- get user score
- … (100s more applicable functions)
Object dot Notations
- What alternative techniques do we have for creating objects?
- Creating user2 user ‘dot notation’
1 | const user2 = {}; //create an empty object |
Object.create
- Creating user3 using Object.create
1 | const user3 = Object.create(null); |
- Our code is getting repetitive, we’re breaking our DRY principle And suppose we have millions of users!
- What could we do?
Creating Objects with Functions
Solution 1. Generate objects using a function
1 | function userCreator(name, score) { |
这个方法有一个问题就是在每一个对象实例上都创造各自的increment方法,很明显这回浪费内存空间,并且修改起来也比较麻烦,
Prototype & New
Avoid Duplication with Prototype
Problems:
- Each time we create a new user we make space in our computer’s memory for all our data and functions. But our functions are just copies
- Is there a better way?
Benefits:
- It’s simple and easy to reason about!
Solution 2:
- Store the increment function in just one object and have the interpreter, if it doesn’t find the function on user1, look up to that object to check if it’s there
- How to make this link?
Using the prototype chain
1 | const functionStore = { |
Link user1 and functionStore so the interpreter, on not finding .increment, makes sure to check up in functionStore where it would find it
Solution 2 in full
1 | const userFunctionStore = { |
Prototype Walkthrough
这里强调了一下看代码是跳过函数定义,直到调用时再看函数体内的代码。
Prototype Chain
- Problem
- No problems! It’s beautiful Maybe a little long-winded
- const newUser = Object.create(functionStore);
- …
- return newUser
- Write this every single time - but it’s 6 words! Super sophisticated but not + standard
new & this Keyword
Solution 3 - Introducing the keyword that automates the hard work: new
const user1 = new userCreator("Phil", 4)
When we call the constructor function with new in front we automate 2 things
- Create a new user object
- return the new user object
But now we need to adjust how we write the body of userCreator - how can we:
- Refer to the auto-created object?
- Know where to put our single copies of functions?
The new keyword automates a lot of our manual work
1 | function userCreator(name, score) { |
Functions are Objects & Functions
Interlude - functions are both objects and functions :/
1 | function multiplyBy2(num){ |
We could use the fact that all functions have a default property on their object version, ’prototype’, which is itself an object - to replace our functionStore object
new Keyword & Share Function with prototype
Complete Solution 3
1 | function UserCreator(name, score){ |
根据《JavaScript高级程序设计》,new
关键字会自动执行以下四个步骤
- 创建一个新对象
- 将函数的作用域指向这个新对象(因此函数内的
this
也就指向了新对象) - 执行函数体中的代码
- 返回新对象
为了共享同一属性或者方法,避免在每个实例上都创建新的副本,js设计了原型链机制。
函数作为一等公民,同时也是一个对象,那么就可以给函数添加属性。每个函数在创建时都会自动添加一个prototype
属性,在我们使用new
关键字调用函数时,同时也会给使用new
关键字创建的对象的__proto__
(浏览器实现,规范为[[proto]]
)属性指向函数的prototype
属性。
函数的prototype
属性指向了一个对象,那么使用new
关键字调用函数后,函数和函数所返回的新对象都指向了同一个对象。
每次使用new
关键字都会创建一个新的对象,对象内包含独立的属性和方法,但所有函数使用new
创建的对象都有一个属性指向了函数的prototype
属性。
js又有着原型链机制,既在当前对象的属性和方法中找不到所调用的属性或方法时不会立即报错,而是会在对象的__proto__
(浏览器实现,规范为[[proto]]
)属性寻找。这样,我们就可以使用函数的prototype
属性所指向的对象中的属性和方法,既多个对象共享了同一对象的属性和方法。这些属性和方法可以理解为公共属性和方法。
Review of new
Benefits
— Faster to write
— Still typical practice in professional code
— 99% of developers have no idea how it works and therefore fail interviews
— We have to upper case first letter of the function so we know it requires ‘new’ to work!
我们在函数的prototype
创建公共的属性和方法,在方法中我们需要操作当前实例的属性和方法,这就需要this
关键字。
this
关键字默认指向函数调用时的作用域,使用new
关键字后,我们将实例的原型与函数的原型指向了同一对象,这样我们就可以通过新创建的对象调用公共方法,此时this
指向了调用的对象。
但是如果我们不适用new
关键字调用函数,此时函数就是普通函数,函数体内的this
也就指向了全局对象(浏览器内是window
,node里应该是global
)。我们称创建对象的函数为构造函数,在es6之前约定函数名首字母要大写,这样来区分构造函数与普通函数,但只是人为约定,即使不使用new
关键字也能调用构造函数(毕竟只是普通函数)。
Scope & this
Calling Prototype Methods
对象方法内的this
指向调用时所在的对象既点方法调用时点左侧的对象,函数内的this
一般指向全局对象。
this Keyword Scoping Issues
What if we want to organize our code inside one of our shared functions - perhaps by defining a new inner function
1 | function UserCreator(name, score){ |
为了避免上述情况的发生,我们可以使用临时变量保存this
的指向,例如在increment
内const self = this
,也可以使用bind
,call
,apply
方法显式绑定this
指向。
We need to introduce arrow functions - which bind this lexically
在es6中,提出了新的函数定义方法,既箭头函数。根据规范,箭头函数并不存在this
关键字,所以箭头函数内的this
就会通过作用域链指向箭头函数外的this
,也就是increment
中的this
。
Solving Scope with Arrow Functions
1 | function UserCreator(name, score){ |
ES6 class Keyword
Solution 4
We’re writing our shared methods separately from our object ‘constructor’ itself (off in the User.prototype object)
Other languages let us do this all in one place. ES2015 lets us do so too
The class ‘syntactic sugar’
1 | class UserCreator { |
- Benefits:
- Emerging as a new standard
- Feels more like style of other languages (e.g. Python)
Problems - 99% of developers have no idea how it works and therefore fail interviews
Default Prototype Chain
Objects default protp
- JavaScript uses this proto link to give objects, functions and arrays a bunch of bonus functionality. All objects by default have
__proto__
1 | const obj = { |
- With Object.create we override the default
__proto__
reference to
Object.prototype and replace with functionStore - But functionStore is an object so it has a
__proto__
reference toObject.prototype
- we just intercede in the chain
Function.prototype and Array.prototype
Arrays and functions are also objects so they get access to all the functions in Object.prototype but also more goodies
1 | function multiplyBy2(num){ |
Subclassing with Factory Functions
Into to Subclassing and Inheritance
面向对象有三个比较显著的特性,封装,继承和多态。
使用ES6的class
语法糖,我们可以将代码更加简洁的组织在一起,也就是封装。
那么在实现一个类之后,我们希望其他类继承现有类的属性,那么应该如何实现呢?
We can achieve this in JavaScript in Solution 2, 3 and 4
在之前的《JavaScript Deep Foundation》中,我们学到了一下知识:
我们都知道js是一门基于对象的语言。在一些面相对象的语言中,我们会创建一个类,在使用时创造一个类的实例来进行使用。类就类似图纸,实例就类似根据图纸造出来的建筑。我们没法直接使用图纸,但是可以使用建筑,同时,我们的建筑时根据图纸制造的,可以造的不同,但是一些图纸上的特性,建筑上也存在。
js原型系统的关键在于实例并没有复制构造函数的原型对象,而是将实例的原型与构造函数的原型对象链接到了一起。
在传统的面向对象语言中,实例复制了公共的属性方法。在js中,实例连接到了存储着公共属性及方法的对象。所以js的设计模式与传统的面相对象语言的设计模式并不相同。
在js的设计模式并不是“继承”,更像是“委托”。js使用原型系统或者说委托系统,而不是类系统或者说继承系统。
Create object with Factory Function
Subclassing in Solution 2 - Factory function approach
1 | function userCreator(name, score) { |
Creating an object with a Sub-Factory Function
1 | function paidUserCreator(paidName, paidScore, accountBalance) { |
Call and Apply
Interlude - We have another way of running a function that allow us to control the assignment of this
1 | const obj = { |
this
always refers to the object to the left of the dot on which the function (method) is being called - unless we override that by running the function using.call()
or.apply()
which lets us set the value ofthis
inside of the increment function
Subclassing with new and call
Create an Object with new
Subclassing in Solution 3 - Constructor(Pseudoclassical) approach
1 | function userCreator(name, score) { |
Creating a Subclass with a Constructor
1 | function paidUserCreator(paidName, paidScore, accountBalance) { |
Subclassing with class, extends & super
Create an Object with a class
Subclassing in Solution 3 - ES2015 Class approach
1 | class userCreater { |
Creating a Subclass with a extends
1 | class paidUserCreator extends userCreater { // 使用extends将paidUserCreator的prototype的__proto__连接到userCreator的prototype上 |