Open yshaojun opened 5 years ago
JavaScript 与 C-like 语言相比,有着如下3个特有或特别的概念,形成其他语言开发者学习 JavaScript 的三大门槛,经历由 Python 开发转前端的我对此深有体会。
ES6 之前,JavaScript 是没有 class 关键字的(更别提 extends),但却有 对象(Object) 的概念,那么如何实现对象间的继承呢?JavaScript 使用一种简单粗暴的方式,直接将要继承的对象挂载在新对象的某个属性上,而这个被继承的对象就叫新对象的 原型(prototype) 。
class
extends
由于原型也是对象,那它也可以有原型,由此形成所谓的 原型链(prototype chain)。
在语言实现上,访问原型并不需要指明新对象上对应的属性名,当访问一个对象属性时,会先在对象本身查找;如果没有该属性,就在对象的原型上查找;如果原型上也没有,就在原型的原型上查找,一直往上。
下面是典型的定义和使用类方式,如果真的不理解 prototype,记住这是 JavaScript 定义类的写法也行:
prototype
function F (a) { this.a = a } F.prototype.print = function () { console.log(this.a) } const c = new F('hello') // 对象 c 的原型就是 F.prototype c.print() // output: hello
这里还有另外2个东西需要注意下:
__proto__:上文提到“将要继承的对象挂载在新对象的某个属性上”,那么这个属性名是什么呢?bingo! 正是 __proto__。
__proto__
c.__proto__ === F.prototype // true
constructor: 一个函数(比如上面的 F)的 prototype 有个 constructor 属性,默认(可以改)指向函数本身。
constructor
F
F.prototype.constructor === F // true // 由于 c.__proto__ === F.prototype,所以: c.constructor === F
判断原型有没过关有个简单的办法:能不能看懂 jQuery 的无 new 构造 jQuery.fn.init。相关代码 init.js、core.js,如果不熟 AMD 模块,可以去看打包后的 jQuery。
new
jQuery.fn.init
当然,ES6 新增了 class/extends 关键字,基本覆盖了原型使用场景,原型的概念也在弱化,但是如果想读一些开源库源码,原型知识还是很必要的。
C-like 语言只有在类、对象里会出现 this,但是 JavaScript 的 this 非常灵活,几乎可以在任何位置出现,那么如何准确的判断函数体里 this 指向呢?犀牛书里总结的很好:
this
函数调用(直接调用):严格模式下指向 undefined,非严格模式下指向全局对象(浏览器:window,Node:global);
undefined
window
global
方法调用(. 运算符调用):. 指向该对象;
.
构造调用(new 运算符调用):指向该新创建的对象;
间接调用:bind/call/apply 指向传入的对象。
bind
call
apply
加上 ES6 的箭头函数:
箭头函数:指向定义时所在的对象,而不是使用时所在的对象。
记住这5条,this 指向问题就过关了。
this 本质上仍然是传参,这个参数叫 上下文(context),其实更推荐通过形参传递 context,使代码清晰易懂:
context
function f1 (b) { console.log(this.a + b) } f1.bind({ a: 1 })(1) // output: 2 function f2 (ctx, b) { console.log(ctx.a + b) } f2({ a: 1 }, 1) // output: 2
JavaScript 长期以来都运行在浏览器单线程里,因此天生支持异步,在“回调地狱”被广为诟病的情况下,Promise 方案应运而生。
Promise
const p = new Promise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000) }) p.then(r => { console.log(r) // output: resolved })
理解 Promise,需要先记住如下几点:
then
thenable
下一条就很重要了:
p.then(r => { console.log(r) // output: resolved return 'common value' }).then(v => { console.log(v) // output: common value return new Promise(resolve => { setTimeout(() => resolve('resolved value'), 1000) }) }).then(v => { console.log(v) // output: resolved value }).then(v => { console.log(v) // output: undefined })
顺便提一下 async/await 语法,也有这样的“规律”:
async
await
如果 await 后面跟的是普通值,那么结果就是这个值;如果 await 后面跟的是 Promise,那么结果就是这个 Promise resolve 的值。
async function f () { const t1 = await 'common value' console.log(t1) // output: common value const t2 = await new Promise(resolve => { setTimeout(() => resolve('resolved value'), 1000) }) console.log(t2) // output: resolved value return 'result' } f().then(r => { console.log(r) // output: result })
任何 async 函数的返回值总是一个 Promise,该 Promise resolve 的值即是函数的返回值。
判断 Promise 有没有过关也有个简单的办法:使用 fs Promises API 写目录遍历函数,生成一个 json 目录树。
过了这三个槛,其他内容基本和 C-like 语言大同小异了,配合 MDN 文档,精准把握每一行 JavaScript 代码就在眼前。
JavaScript 与 C-like 语言相比,有着如下3个特有或特别的概念,形成其他语言开发者学习 JavaScript 的三大门槛,经历由 Python 开发转前端的我对此深有体会。
原型继承(prototype)
ES6 之前,JavaScript 是没有
class
关键字的(更别提extends
),但却有 对象(Object) 的概念,那么如何实现对象间的继承呢?JavaScript 使用一种简单粗暴的方式,直接将要继承的对象挂载在新对象的某个属性上,而这个被继承的对象就叫新对象的 原型(prototype) 。由于原型也是对象,那它也可以有原型,由此形成所谓的 原型链(prototype chain)。
在语言实现上,访问原型并不需要指明新对象上对应的属性名,当访问一个对象属性时,会先在对象本身查找;如果没有该属性,就在对象的原型上查找;如果原型上也没有,就在原型的原型上查找,一直往上。
下面是典型的定义和使用类方式,如果真的不理解
prototype
,记住这是 JavaScript 定义类的写法也行:这里还有另外2个东西需要注意下:
__proto__
:上文提到“将要继承的对象挂载在新对象的某个属性上”,那么这个属性名是什么呢?bingo! 正是__proto__
。constructor
: 一个函数(比如上面的F
)的prototype
有个constructor
属性,默认(可以改)指向函数本身。判断原型有没过关有个简单的办法:能不能看懂 jQuery 的无
new
构造jQuery.fn.init
。相关代码 init.js、core.js,如果不熟 AMD 模块,可以去看打包后的 jQuery。当然,ES6 新增了
class
/extends
关键字,基本覆盖了原型使用场景,原型的概念也在弱化,但是如果想读一些开源库源码,原型知识还是很必要的。this 指向
C-like 语言只有在类、对象里会出现
this
,但是 JavaScript 的this
非常灵活,几乎可以在任何位置出现,那么如何准确的判断函数体里this
指向呢?犀牛书里总结的很好:函数调用(直接调用):严格模式下指向
undefined
,非严格模式下指向全局对象(浏览器:window
,Node:global
);方法调用(
.
运算符调用):.
指向该对象;构造调用(
new
运算符调用):指向该新创建的对象;间接调用:
bind
/call
/apply
指向传入的对象。加上 ES6 的箭头函数:
箭头函数:指向定义时所在的对象,而不是使用时所在的对象。
记住这5条,
this
指向问题就过关了。this
本质上仍然是传参,这个参数叫 上下文(context),其实更推荐通过形参传递context
,使代码清晰易懂:Promise
JavaScript 长期以来都运行在浏览器单线程里,因此天生支持异步,在“回调地狱”被广为诟病的情况下,
Promise
方案应运而生。理解
Promise
,需要先记住如下几点:Promise
是一个拥有then
方法的对象(thenable
对象);then
方法返回的仍然是一个Promise
(所以可以连写多个then
);下一条就很重要了:
then
函数里返回的是普通值,那么下一个then
函数里拿到的参数就是这个值;如果上一个then
函数里返回的是Promise
,那么下一个函数里拿到的参数就是这个Promise
resolve 的值。顺便提一下
async
/await
语法,也有这样的“规律”:如果
await
后面跟的是普通值,那么结果就是这个值;如果await
后面跟的是Promise
,那么结果就是这个Promise
resolve 的值。任何
async
函数的返回值总是一个Promise
,该Promise
resolve 的值即是函数的返回值。判断
Promise
有没有过关也有个简单的办法:使用 fs Promises API 写目录遍历函数,生成一个 json 目录树。过了这三个槛,其他内容基本和 C-like 语言大同小异了,配合 MDN 文档,精准把握每一行 JavaScript 代码就在眼前。