第 7 题:ES5/ES6 的继承除了写法以外还有什么区别?
class
声明会提升,但不会初始化赋值。Foo
进入暂时性死区,类似于 let
、const
声明变量。
const bar = new Bar(); // it's ok function Bar() { this.bar = 42; } const foo = new Foo(); // ReferenceError: Foo is not defined class Foo { constructor() { this.foo = 42; } }
class
声明内部会启用严格模式。
// 引用一个未声明的变量 function Bar() { baz = 42; // it's ok } const bar = new Bar(); class Foo { constructor() { fol = 42; // ReferenceError: fol is not defined } } const foo = new Foo();
class
的所有方法(包括静态方法和实例方法)都是不可枚举的。
// 引用一个未声明的变量 function Bar() { this.bar = 42; } Bar.answer = function() { return 42; }; Bar.prototype.print = function() { console.log(this.bar); }; const barKeys = Object.keys(Bar); // ['answer'] const barProtoKeys = Object.keys(Bar.prototype); // ['print'] class Foo { constructor() { this.foo = 42; } static answer() { return 42; } print() { console.log(this.foo); } } const fooKeys = Object.keys(Foo); // [] const fooProtoKeys = Object.keys(Foo.prototype); // []
class
的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]]
,不能使用 new
来调用。
function Bar() { this.bar = 42; } Bar.prototype.print = function() { console.log(this.bar); }; const bar = new Bar(); const barPrint = new bar.print(); // it's ok class Foo { constructor() { this.foo = 42; } print() { console.log(this.foo); } } const foo = new Foo(); const fooPrint = new foo.print(); // TypeError: foo.print is not a constructor
必须使用 new
调用 class
。
function Bar() { this.bar = 42; } const bar = Bar(); // it's ok class Foo { constructor() { this.foo = 42; } } const foo = Foo(); // TypeError: Class constructor Foo cannot be invoked without 'new'
class
内部无法重写类名。
function Bar() { Bar = 'Baz'; // it's ok this.bar = 42; } const bar = new Bar(); // Bar: 'Baz' // bar: Bar {bar: 42} class Foo { constructor() { this.foo = 42; Foo = 'Fol'; // TypeError: Assignment to constant variable } } const foo = new Foo(); Foo = 'Fol'; // it's ok
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(51)
@MingShined 什么是面向类的语言?第一次听说,能否详细讲讲。
JS一直以来没有被正确的理解,由于诞生的时间晚,相比于c、java等一类面向类的语言,JS没有真正意义上的类的概念。加上最早开始使用JS的开发者大多数都是其他类语言的转型,他们不够理解JS这种面向对象的模式,所以只能通过一些笨拙的方式去实现所谓的类,从而实现继承和多态,这种模式就是我们常见的prototype。
实际上无论是es5的prototype模拟类还是es6的语法糖class,都不是真正意义上的类。因为在类的实现中,子类是对父类的完全复制,而js不是,换句话讲,如果我们在改变了js一个父类的方法,继承该父类的子类和所有实例都会发生改变。ES6class的实现,本质上还是通过Object.crete()去关联两者的prototype。
JS的正确用法应该是面向对象,行为委托,而不是模拟类。
以上是我的一些理解,有什么误人之处,希望指出,感激不尽。
刚好今天在看红宝书,顺便放下自己总结的ES5的继承
@MingShined 在原型链继承中test.age 输出结果应该是24啊,这里手误吧
@Jesse121 感谢这位同学指出。已经修改了
function Bar() {
this.bar = 42;
}
const bar = Bar(); // it's ok
这个只是写法上可以这样吧?bar 仍然是undefined
首先感谢作者,写的很好,受益匪浅。
其次建议作者修改第一条,虽然我理解了你想表达啥,不过也是看了你和其他人的讨论才明白你的意思。
我理解function和class的声明和赋值是不可分割的,这和var不一样,所以你例举的例子本身就不成立,硬生的去拆解声明和赋值这两个流程对class和function没有意义,只对var有意义。
参考MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
这点区别应该是call方法导致的,使用prototype写法是不会有差别的,同样会继承内置对象
确实是call方法导致的,不知道大家为何都说是this生成的顺序不同导致的
class没有变量提升吧,不然这个怎么解释
{ console.log(Foo) // Uncaught ReferenceError: Cannot access 'Foo' before initialization class Foo { constructor() { this.foo = 37; } } }
{ console.log(Bar) //undefined var Bar = function(){} }
这个图不对啊, A类实例的
__proto__
指向的是A类的prototype
先来复习下两者的写法区别:
原型链继承
ES6继承
继承的主要区别:
原型链继承里的
smallHero.__proto__ === Function.prototype
ES6的class里的
smallHero11.__proto__ === Hero
, 子类可以直接通过 proto 寻址到父类没提升吧 提升了没赋值应该是 typeError 不该是 ReferenceError ,ReferenceError说明就没定义
这个问题不会这么理解的吧 分析变量foo = new Foo()的时候{}这个块级作用域分析的时候内部有Foo因为变量不提升导致的暂时性死区导致Foo ReferenceError,如果提升但是没有值或者是为undefined,像函数那样调用该是报错TypeError
学到了学到了,小姐姐真厉害~
应该是 children.proto === parent 吧.
children 和 parent 都是对象,没有prototype属性,你这children.prototype 和 parent.prototype都是undefined
但是calss只提升不会赋值,你的这段代码是默认会提升而且赋值了
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
Source:阮一峰,ES6
es6 继承调用/不调用super区别在于:
当一个普通的构造函数运行时,它会创建一个空对象作为 this,然后继续运行。
但是当派生的构造函数运行时,与上面说的不同,它指望父构造函数来完成这项工作。
所以如果我们正在构造我们自己的构造函数,那么我们必须调用 super,否则具有 this 的对象将不被创建,并报错。
补充一点:类继承是单一继承结构,只有一个父类;而原型继承本质上是组合,它可以有多个父类,且不会产生层级分类这样的副作用。
实在不明白为什么第二个构造函数继承可以被叫做继承,因为一个叫 Parent 一个叫 Children?还是因为 Parent.call(this)?
@afishhhhh 第二个例子作者没写全,这里的Child只继承了Parent类的实例属性和方法,但是没有说父类原型怎么处理,当然如果Parent本身就没有定义原型,这个例子也是没问题的。
关于提升,TDZ 这方面的东西我觉得可以通过词法环境的相关内容来解释,这样是比较清楚的,而且关于 TDZ 我没有在规范里找到,所以我理解为 TDZ 是为了帮助我们理解而提出来的一个术语。
afishhhhh/blog#10
很关键的一点 很核心
用es5要实现内置对象的继承
没错,敲代码验证了一下:
ES5的寄生组合式继承:
对应的ES6 class的继承:
测试了两种继承 子类实例的行为是一致的。
class不会提升,文档没问题。而且TDZ和变量提升好像没关系,只是ES6的一个规定而已。
不过既然你放在一起举例子,那第二个例子我把class改成let,你瞅瞅是不是好理解点
class不会提升,文档没问题。而且TDZ和变量提升好像没关系,只是ES6的一个规定而已。
不过既然你放在一起举例子,那第二个例子我把class改成let,你瞅瞅是不是好理解点
众所周知 let是不会被提升的,而且你第二个例子的报错信息写错了,应该是
Uncaught ReferenceError: Cannot access 'Foo' before initialization
而不是ReferenceError: Foo is not defined
,因为触发了TDZ。这个问题有问题,class本身就是语法糖,最后也是被转化成es5,所以问这个问题有什么意义?
@alanchanzm
这个结论似乎和
class
没有关联,关键在于声明class
方法时使用了=>
(箭头函数):class
上只有静态方法和原型方法不存在prototype
@alanchanzm 说class声明会提升但不会初始化赋值是错的. both class declarations and class expressions are not hoisted(类声明和类表达式都不会存在提升) 出自 https://leanpub.com/understandinges6/read
区别:
这也是为什么ES6的继承必须先调用super方法,因为这一步会生成一个继承父类的this的对象,没有这一步就无法继承父类。
(真的是牛鬼蛇神)
因为this生成顺序不同,所以需要在constructor中,需要使用super()
@XueSeason 我好像记得es6
class Sub extends Super {}
在babel解析中是这样的JavaScript相比于其他面向类的语言,在实现继承时并没有真正对构造类进行复制,当我们使用
var children = new Parent()
继承父类时,我们理所当然的理解为children ”为parent所构造“。实际上这是一种错误的理解。严格来说,JS才是真正的面向对象语言,而不是面向类语言。它所实现的继承,都是通过每个对象创建之初就存在的prototype属性进行关联、委托,从而建立练习,间接的实现继承,实际上不会复制父类。1.原型链继承
我们可以发现,整个继承过程,都是通过原型链之间的指向进行委托关联,直到最后形成了”由构造函数所构造“的结局。
2.构造函数继承
构造继承关键在于,通过在子类的内部调用父类,即通过使用apply()或call()方法可以在将来新创建的对象上获取父类的成员和方法。
ES6中新增了class关键字来定义类,通过保留的关键字extends实现了继承。实际上这些关键字只是一些语法糖,底层实现还是通过原型链之间的委托关联关系实现继承。
区别于ES5的继承,ES6的继承实现在于使用super关键字调用父类,反观ES5是通过call或者apply回调方法调用父类。
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
因为this生成顺序不同,所以需要在constructor中,需要使用super()
@XueSeason 哈哈哈,审题不清,这轮面试要挂了。
再补充一点:
ES5 和 ES6 子类
this
生成顺序不同。ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例,ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象。@alanchanzm 答了很多,而且很有帮助,但是离题了。
问题是继承的差异。
子类可以直接通过 __proto__ 寻址到父类。
而通过 ES5 的方式,Sub.__proto__ === Function.prototype
@labike
可能是我们对「提升」的理解不同吧?我理解的「提升」和「赋值」是两个过程。
我拆解一下那个例子:
类似于以下代码(但不等于):
@alanchanzm 我觉得不对吧
class会提升这段代码就说不过去!
class
@labike
原文有问题,
class
是会提升的,其表现与let
、const
类似,变量名会进入TDZ。看下例:如果没有提升,
foo
会是块作用域外的Foo
实例。但是由于提升的关系,块作用域内的Foo
遮蔽了外层的同名函数。@alanchanzm 1.
class
声明会提升 . 是不是写错了?原文: Class declarations, unlike function declarations, are not hoisted.