-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 7 期:ES5/ES6 的继承除了写法以外还有什么区别? #20
Comments
@alanchanzm 1. |
@labike var Foo = function() {
this.foo = 21;
};
{
const foo = new Foo(); // ReferenceError: Foo is not defined
class Foo {
constructor() {
this.foo = 37;
}
}
} |
@alanchanzm 我觉得不对吧
class会提升这段代码就说不过去! |
@labike var Foo = function() { /** pass */ };
{
// 「块作用域」内可以访问全局变量 Foo
const foo = new Foo();
} var Foo = function() { /** pass */ };
{
// 「块作用域」内无法访问全局变量 Foo,因为它被本作用域内的 Foo 遮蔽了
// 如果 class 不会提升的话,new Foo() 应该成功调用
const foo = new Foo(); // ReferenceError: Foo is not defined
class Foo{ /** pass */ }
} 类似于以下代码(但不等于): var Foo = function() { /** pass */ };
{
let Foo; // 区别在于此处 Foo 已经初始化为 undefined
// 「块作用域」内无法访问全局变量 Foo,因为它被本作用域内的 Foo 遮蔽了
const foo = new Foo();
Foo = class { /** pass */}
} |
@alanchanzm 答了很多,而且很有帮助,但是离题了。 问题是继承的差异。 class Super {}
class Sub extends Super {}
const sub = new Sub();
Sub.__proto__ === Super; 子类可以直接通过 __proto__ 寻址到父类。 function Super() {}
function Sub() {}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
Sub.__proto__ === Function.prototype; 而通过 ES5 的方式,Sub.__proto__ === Function.prototype |
@XueSeason 哈哈哈,审题不清,这轮面试要挂了。 function MyES5Array() {
Array.call(this, arguments);
}
// it's useless
const arrayES5 = new MyES5Array(3); // arrayES5: MyES5Array {}
class MyES6Array extends Array {}
// it's ok
const arrayES6 = new MyES6Array(3); // arrayES6: MyES6Array(3) [] |
因为this生成顺序不同,所以需要在constructor中,需要使用super() |
10 similar comments
因为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 我好像记得es6 function Super(){}
let Sub = Object.create(Super)
Sub.__proto__ === Super;//true |
JavaScript相比于其他面向类的语言,在实现继承时并没有真正对构造类进行复制,当我们使用
1.原型链继承
我们可以发现,整个继承过程,都是通过原型链之间的指向进行委托关联,直到最后形成了”由构造函数所构造“的结局。 2.构造函数继承
构造继承关键在于,通过在子类的内部调用父类,即通过使用apply()或call()方法可以在将来新创建的对象上获取父类的成员和方法。
ES6中新增了class关键字来定义类,通过保留的关键字extends实现了继承。实际上这些关键字只是一些语法糖,底层实现还是通过原型链之间的委托关联关系实现继承。
区别于ES5的继承,ES6的继承实现在于使用super关键字调用父类,反观ES5是通过call或者apply回调方法调用父类。 |
@MingShined 什么是面向类的语言?第一次听说,能否详细讲讲。 |
JS一直以来没有被正确的理解,由于诞生的时间晚,相比于c、java等一类面向类的语言,JS没有真正意义上的类的概念。加上最早开始使用JS的开发者大多数都是其他类语言的转型,他们不够理解JS这种面向对象的模式,所以只能通过一些笨拙的方式去实现所谓的类,从而实现继承和多态,这种模式就是我们常见的prototype。
以上是我的一些理解,有什么误人之处,希望指出,感激不尽。 |
刚好今天在看红宝书,顺便放下自己总结的ES5的继承 // 寄生组合式继承
// 通过借用构造函数来继承属性, 通过原型链来继承方法
// 不必为了指定子类型的原型而调用父类型的构造函数,我们只需要父类型的一个副本而已
// 本质上就是使用寄生式继承来继承超类型的原型, 然后再讲结果指定给子类型的原型
function object(o){ // ===Object.create()
function F(){};
F.prototype = o;
return new F();
}
function c1(name) {
this.name = name;
this.color = ['red', 'green'];
}
c1.prototype.sayName = function () {
console.log(this.name);
}
function c2(name, age) {
c1.call(this, name)
this.age = age
}
// 第一步:创建父类型原型的一个副本
// 第二步:为创建的副本添加 constructor 属性, 从而弥补因重写原型而失去的默认的 constructor 属性
// 第三步:将新创建的对象(即副本)赋值给子类型的原型
function inheritPrototype(superType, subType) {
const prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
inheritPrototype(c1, c2);
// c2的方法必须放在寄生继承之后
c2.prototype.sayAge = function () {
console.log(this.age);
} |
@MingShined 在原型链继承中test.age 输出结果应该是24啊,这里手误吧 |
@Jesse121 感谢这位同学指出。已经修改了 |
class没有变量提升吧,不然这个怎么解释
|
先来复习下两者的写法区别: 原型链继承
ES6继承
继承的主要区别:原型链继承里的
ES6的class里的
|
没提升吧 提升了没赋值应该是 typeError 不该是 ReferenceError ,ReferenceError说明就没定义
这个问题不会这么理解的吧 分析变量foo = new Foo()的时候{}这个块级作用域分析的时候内部有Foo因为变量不提升导致的暂时性死区导致Foo ReferenceError,如果提升但是没有值或者是为undefined,像函数那样调用该是报错TypeError |
学到了学到了,小姐姐真厉害~ |
应该是 children.proto === parent 吧. |
但是calss只提升不会赋值,你的这段代码是默认会提升而且赋值了 |
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。 es6 继承调用/不调用super区别在于: |
|
补充一点:类继承是单一继承结构,只有一个父类;而原型继承本质上是组合,它可以有多个父类,且不会产生层级分类这样的副作用。 |
实在不明白为什么第二个构造函数继承可以被叫做继承,因为一个叫 Parent 一个叫 Children?还是因为 Parent.call(this)? |
@afishhhhh 第二个例子作者没写全,这里的Child只继承了Parent类的实例属性和方法,但是没有说父类原型怎么处理,当然如果Parent本身就没有定义原型,这个例子也是没问题的。 // 定义父类
function Parent(value) {
this.language = ["javascript", "react", "node.js"];
this.value = value;
}
// 如果Parent也定义了prototype
Parent.prototype = {
getValue() {
return this.value;
},
};
// 定义子类
function Children() {
Parent.apply(this, arguments);
}
// 这里要继承父类的原型
Children.prototype = Object.create(Parent.prototype); |
关于提升,TDZ 这方面的东西我觉得可以通过词法环境的相关内容来解释,这样是比较清楚的,而且关于 TDZ 我没有在规范里找到,所以我理解为 TDZ 是为了帮助我们理解而提出来的一个术语。 |
很关键的一点 很核心 |
用es5要实现内置对象的继承 function MyDate() {
// Date 上的方法只能由 Date 的实例调用,所以new MyDate的时候要返回一个date对象
// const date = new Date(...arguments)
const date = new (Function.prototype.bind.apply(Date, [null].concat(Array.prototype.slice.call(arguments))));
Object.setPrototypeOf(date,MyDate.prototype);
return date;
}
Object.setPrototypeOf(MyDate.prototype, Date.prototype)
MyDate.prototype.getTime = function() {
const year = this.getFullYear();
const month = this.getMonth() + 1;
const day = this.getDate();
return `${year}-${month}-${day}`;
}
const newDate = new MyDate();
console.log(newDate.getTime()); |
没错,敲代码验证了一下: ES5的寄生组合式继承:
对应的ES6 class的继承:
测试了两种继承 子类实例的行为是一致的。 |
class不会提升,文档没问题。而且TDZ和变量提升好像没关系,只是ES6的一个规定而已。 var a = 1
{
console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 2
} class不会提升,文档没问题。而且TDZ和变量提升好像没关系,只是ES6的一个规定而已。 var a = 1
{
console.log(a) // Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 2
} 众所周知 let是不会被提升的,而且你第二个例子的报错信息写错了,应该是
// var a会被提升,所以执行a()时报TypeError的错误,因为由于提升,可以找到a这个变量,只是调用的时候错误了
a() // Uncaught TypeError: a is not a function
var a = function() {}
// let a不会被提升,所以执行a()报 ReferenceError,不会提升,根本找不到a这个变量
a() // Uncaught ReferenceError: a is not defined
let a = function() {}
// 和上面一样,class Foo{} 不会被提升
const foo = new Foo() // Uncaught ReferenceError: Foo is not defined
class Foo{} |
这个问题有问题,class本身就是语法糖,最后也是被转化成es5,所以问这个问题有什么意义? |
这个结论似乎和 class A {
static a = function() {}
b = function() {}
}
const a = new A()
const result = new a.b() |
|
@alanchanzm 说class声明会提升但不会初始化赋值是错的. both class declarations and class expressions are not hoisted(类声明和类表达式都不会存在提升) 出自 https://leanpub.com/understandinges6/read#leanpub-auto-class-declarations |
|
区别:
这也是为什么ES6的继承必须先调用super方法,因为这一步会生成一个继承父类的this的对象,没有这一步就无法继承父类。 |
let、const、import、class声明的变量不存在变量提升 |
帖子第一条答案完美避开了核心区别😂(真是一点都没提到啊……) |
Uncaught ReferenceError: Cannot access 'Foo' before initialization at |
提升这个肯定有问题,我还以为我看错了,问了GPT 也是不能被提升 |
来源:Understanding ECMAScript 6
class
声明会提升,但不会初始化赋值。Foo
进入暂时性死区,类似于let
、const
声明变量。class
声明内部会启用严格模式。class
的所有方法(包括静态方法和实例方法)都是不可枚举的。class
的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]]
,不能使用new
来调用。new
调用class
。class
内部无法重写类名。The text was updated successfully, but these errors were encountered: