-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
JavaScript深入之继承的多种方式和优缺点 #16
Comments
博主还没写事件循环和浏览器渲染机制这块,借宝地这里有个问题提前探讨一下。 <html>
<body>
<h2>Hello</h2>
<script>
function printH2() {
console.log('first script', document.querySelectorAll('h2'));
}
printH2()
setTimeout(printH2)
</script>
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/4.0.0-alpha.4/css/bootstrap.css">
<h2>World</h2>
<script>
console.log('second script');
</script>
</body>
</html> 这个demo的效果是:控制台先打印出来了printH2(),setTimeout(printH2)的结果(说明此时的DOMtree已经完成),然后浏览器显示了页面,页面完全显示之后(RenderTree完成)才执行了 console.log('second script'); 有个地方不明白,就是js的异步任务与UI线程之间的调度,这个demo的结果来看,DOM节点渲染之后生成DOMtree才开始执行 setTimeout(printH2)的printH2函数,所以打印出了两个h2节点。 |
哈哈,这道题难住我了,对这个方面并没有研究,抱歉也提供不了什么资料,不过我会把这个课题记下来,如果想明白了或者有所进展,立刻跟题主讨论哈~ |
@jawil ,其实不一定是 setTimeout(printH2) 早于 second script 的 照理说second script 应该是会被 css 阻塞执行的。不过之前都是看的 css 在 head 里的表现,放在 body 里是不是也有阻塞一说不太清楚。 只是我测下来的现象 disable cache 的话怎么刷新 second script 肯定在最后,但是把 disable cache 勾选去掉的话,后两个出现的顺序不固定。 浏览器里面的线程也希望有个大神能分享下,之前也遇到过很奇怪的表现,譬如 js 早于 css 或者不晚于 css 10ms 以内,浏览器会等 js 执行完再渲染;如果 晚于 css 10ms 之后网络再收到 js 响应,浏览器会先渲染再执行 js。 |
@shaopower 竟然还有这么奇怪的表现,感觉开启了新的世界,看来要尽快的去探索一下~ |
但是,我不明白的是,既然CSS会延迟脚本的执行,我把bootstrap那个换成内联样式,就是不发送http请求下载那个css样式,好像结果又变了,这时候构建CSStree好像并没有阻塞js脚本的执行,那么,到底是HTTP线程阻塞了脚本的执行,还是构建CSStree阻塞了脚本执行? 如果是HTTP线程阻塞了js的执行,这个也可以解释,为什么多个js并行下载,要等全部下载完成才执行一个道理,我继续尝试了一下,换成img标签,这个也会发送http请求,但是这个并不会阻塞脚本的执行。 如果是构建CSStree阻塞了线程,根据上面实践的结果,发现构建CSStree并没有阻塞脚本的运行。之前看到的下面的这个说法,也就不成立。
那么我的猜测就是,发送的请求是是样式文件会阻塞js脚本的执行,但为什么css会阻塞js脚本文件的执行,这个我暂时也不清楚,还有浏览器预加载一些机制也不太清楚。JS 的执行有可能依赖最新样式???难道我测的结果有问题? |
还有就是别人总结的这句话,一直不太懂,这种循坏机制到底是怎么工作的,描述的也比较抽象。
😨😨😨,浏览器渲染解析线程调度这一块任重而道远。 |
嗯嗯,确实任重而道远,暂时我也没有想明白,还需要学习很多东西,非常感谢你的分析~ o( ̄▽ ̄)d |
我觉得 js 脚本之前如果没有出现过 css 标签,那这部分脚本是不会被 css 文件阻塞的,就是说下面这块代码不会被阻塞 function printH2() {
console.log('first script', document.querySelectorAll('h2'));
}
printH2()
setTimeout(printH2) bootstrap.css 影响阻塞的是 second script,而且我觉得只有外链的 css 才会阻塞 js,内联的不会 css 外链的时候 parse html;
request bootstrap.css;
printH2();
setTimeout(printH2);
if (css 有缓存或者响应快){
//receive css
parse stylesheet;
second script;
paint 页面;
timeout fire;
} else {
timeout fire;
// receive css
parse stylesheet;
second script;
paint 页面;
} 可以看到 second script 是在 parse stylesheet 之后的。 为什么有这个 if else 分支,我猜测浏览器做的某些处理,只是保证不会解析 css 后的 js 加入 event loop,也就不会执行了, 但是已经在里面的还是会执行完吧 css 改成内联后 走的逻辑类似 if css 缓存那个分支,而且没有 parse stylesheet 这一条了,parse html 也有差别,最开始就直接 parse 完了整个页面 具体为什么 css 会延迟 js 执行,我觉得这是浏览器的一种实现方式吧。
另外并行的 js 请求也不是等全部都下载完才执行的哈,如果 script 不带 async 的话,按照在 html 里的上下位置来执行的,当然和网络也有关系。 例如下面的 2个 js 会同时请求 <body>
<script src="1.js"></script>
<script src="2.js"></script>
</body> setTimeout 是属于 macrotask,你的例子里应该和 microtask 关系不大, Promise then 和 MutationObserver 这些算 microtask |
写的很透彻,我得慢慢消化 |
请教一个问题。为什么不让 Child.prototype 直接访问到 Parent.prototype ?一定要通过寄生的方式呢。。。 Parent.prototype.getName = function () { function Child (name, age) { // 关键的三步 //F.prototype = Parent.prototype; //Child.prototype = new F(); var child1 = new Child('kevin', '18'); console.log(child1);` |
@littleluckly |
@shaopower 十分感谢回答,正解,b( ̄▽ ̄)d |
@shaopower @mqyqingfeng 谢谢解惑! |
看了博主的文章,真是受益匪浅,新手想请教两个问题: 1. 2.原型上的constructor属性,除了能让子实例获得正确的父构造函数,感觉并不影响原型链继承?实际开发中是否还有其他用处? 感谢! |
@satorioh Object.create 的模拟实现如下: Object.create = function( o ) {
function f(){}
f.prototype = o;
return new f;
} 使用 关于第二个问题,constructor确实不会影响原型链继承,constructor 用来指向对象关联的函数,我在实际开发中倒没有用过 constructor,值得注意的一点就是如果用一个新对象覆盖函数的 prototype 属性值,新对象没有 constructor 属性,实例不能通过 constructor 指向正确的构造函数。 嗯,好像没有回答什么有用的哈~ |
@mqyqingfeng 好的,还是要感谢博主的耐心解答 |
今天一天一口气看完了博主所有的文章,分析的很透彻。希望能多写文章,多交流,共同学习进度,非常感谢楼主的分享... |
@lynn1824 与你共勉,[]~( ̄▽ ̄)~* |
老铁,看完你文章,以前有好多问题都理清楚啦,看来还是要往底层深入,哈哈,谢谢博主! |
@522363215 恭喜你看完~ []~( ̄▽ ̄)~* JavaScript 还有很多的底层知识需要探索和总结~ 与你共勉~ |
🙏楼主的分享~~~谢谢,对基础再次扎实了~~~ |
今天朋友问了我一个问题,但是我也搞不懂,所以特地来请教博主,希望给予解答。请实现函数Person,该函数实现功能如下:
|
各位大佬请问组合继承中Child.prototype.constructor = Child; 这一步是干嘛的?如果这里加了,第一种原型链继承也可以加这一句吧 |
组合继承可以改成这样吗?
|
原型式继承说是Object.create 的模拟实现并不太准确,Object.create(null)与createObj(null)是有区别的 |
最后一段的“因此避免了在 Parent.prototype 上面创建不必要的、多余的属性”,应该是避免了在 Child.prototype 上创建不必要的、多余的属性吧?有理解错误的地方还请指出。 |
有个地方写错了,你可以在页面搜索一下, |
请问下继承在实际前端开发中有哪些可以运用的地方 |
@jxccc1998 我们写 React 代码的时候,不就经常会用到 extend 吗?我们换个原始具体一点的例子,就比如这个代码 https://github.com/mqyqingfeng/progress-indicator/blob/master/progress-indicator.js 是实现浏览进度条的,你可以发现为了给 ProgressIndicator 添加上事件监听的能力,就是用继承 EventEmitter 来实现的,ProgressIndicator 和 EventEmitter 分工明确,代码更加整洁。 |
感谢博主 很好的例子 |
最后一段的“Parent.prototype”应该是“Child.prototype”上吧 |
// 寄生组合式继承
function Person(name) {
this.name = name
this.color = ['red', 'blue']
}
Person.prototype.sayName = function () { console.log(this.name) }
function Child(name, age) {
Person.call(this, name) // 继承属性
this.age = age
}
Child.prototype = Object.create(Person.prototype) // 原型式继承
Child.prototype.constructor = Child // 指定构造函数
/**
* Object.create 源码
* @param o
*/
function object(o) {
function F() {}
F.prototype = o
return new F()
} |
为什么寄生组合式继承需要执行关键的三步,而不是直接
因为直接执行
如果我说错了还望指出。 |
第一个例子,原型链继承console.log(child1.getName()) // kevin是否换成child1.getName()就可以了🤣🤣🤣 |
这是来自QQ邮箱的假期自动回复邮件。
您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
|
大佬 我这有个小疑问 function Parent() {
this.age = 18;
}
function Child() {}
Child.prototype = new Parent();
var child1 = new Child();
child1.age = 19;
console.log(child1.age); 原型链继承 这种修改age 为什么是在实例上新加了个属性 而不是修改原型的值 |
https://blog.csdn.net/helloschurmi/article/details/95043685 这个博客解释的比较清楚 |
多谢 |
为什么会用寄生这个词的? |
可以从js内存模型的角度理解, 基本类型存在stack里面,引用类型存在堆内存里面, 你的问题 就很容易得到答案了, 为什么改了A.xx 不会影响B . 因为指向的不是一个地址。 |
这是来自QQ邮箱的假期自动回复邮件。
您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
|
写在前面
本文讲解JavaScript各种继承方式和优缺点。
但是注意:
这篇文章更像是笔记,哎,再让我感叹一句:《JavaScript高级程序设计》写得真是太好了!
1.原型链继承
问题:
1.引用类型的属性被所有实例共享,举个例子:
2.在创建 Child 的实例时,不能向Parent传参
2.借用构造函数(经典继承)
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在 Child 中向 Parent 传参
举个例子:
缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。
3.组合继承
原型链继承和经典继承双剑合璧。
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
4.原型式继承
就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
注意:修改
person1.name
的值,person2.name
的值并未发生改变,并不是因为person1
和person2
有独立的 name 值,而是因为person1.name = 'person1'
,给person1
添加了 name 值,并非修改了原型上的 name 值。5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
6. 寄生组合式继承
为了方便大家阅读,在这里重复一下组合继承的代码:
组合继承最大的缺点是会调用两次父构造函数。
一次是设置子类型实例的原型的时候:
一次在创建子类型实例的时候:
回想下 new 的模拟实现,其实在这句中,我们会执行:
在这里,我们又会调用了一次 Parent 构造函数。
所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为
colors
,属性值为['red', 'blue', 'green']
。那么我们该如何精益求精,避免这一次重复调用呢?
如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?
看看如何实现:
最后我们封装一下这个继承方法:
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
相关链接
《JavaScript深入之从原型到原型链》
《JavaScript深入之call和apply的模拟实现》
《JavaScript深入之new的模拟实现》
《JavaScript深入之创建对象》
深入系列
JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。
The text was updated successfully, but these errors were encountered: