先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
正文
script.src = scriptArr[0];
document.body.appendChild(script);
}
loadScript(‘1.js’,‘2.js’,‘3.js’);
如果浏览器不支持支持脚本,那么它会显示出 noscript 元素中的文本。
…
…
Your browser does not support VBScript!
…
…
总结
减少 JavaScript 对性能的影响有以下几种方法:
-
将所有的
<script>
标签放到页面底部,也就是</body>
闭合标签之前,这能确保在脚本执行前页面已经完成了渲染。 -
尽可能地合并脚本。页面中的
<script>
标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。 -
采用无阻塞下载 JavaScript 脚本的方法:
-
使用
<script>
标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本); -
使用动态创建的
<script>
元素来下载并执行代码; -
使用 XHR 对象下载 JavaScript 代码并注入页面中。
通过以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 网站和应用的实际性能。
该模块后续补充,也欢迎大家补充
看高性能js这本书时,有一段话让我很不解他想表达什么意思,如下,既然是放在body底部了为什么还要动态加载?(我的理解:这描述的应该是懒加载,动态加载,你不需要就可以先不加载,
欢迎交流)
================================================================================
通过new创建对象经历4个步骤:
-
1、创建一个新对象
-
2、将构造函数的作用域赋给新对象(因此this指向了这个新对象)
-
3、执行构造函数中的代码(为这个新对象添加属性);
-
4、返回新对象。
function newFunc (name) {
var o = {};
o.proto = Person.prototype;//绑定Person的原型
Person.call(o, name);
return o;
}
function Person(){}
var p1 = new Person()
console.log(p1.constructor)
Person.prototype = {
name:“小红”
}
var p2 = new Person()
console.log(p2.constructor)
答案:
function Person(){}
var p1 = new Person()
console.log(p1.constructor) // [Function: Person]
Person.prototype = {
name:“小红”
}
var p2 = new Person()
console.log(p2.constructor) [Function: Object]
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则,为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性指向了prototype所在的函数。
上面的体题中
打印p1.constructor
时,p1没有constructor
属性,于是就往原型链查找,就在Person.prototype上找到了有constructor
,而这个constructor
指向构造函数Person,因此,打印出[Function: Person]
。
而到p2时,Person.prototype
的值已经被修改成{
name:“小红”
},发现这个值里已经没有constructor,但是prototype上必须有constructor,所以它就自己创建了constructor,并且默认指向Object.,所以打印[Function: Object]
。
第四问:如果我修改prototype时,仍想constructor依旧指向Person,该怎么做呢?
如果想constructor依旧指向Person,可以在修改prototype的时候,添加上construtor属性
function Person(){}
var p1 = new Person()
console.log(p1.constructor) //[Function: Person]
Person.prototype = {
constructor: Person,
name:“小红”
}
var p2 = new Person()
console.log(p2.constructor) //[Function: Person]
但是这种添加constructor属性,会导致它的[[Enumerable]]特性的值被设置为true,所以可以用下面这种方式
function Person(){}
var p1 = new Person()
console.log(p1.constructor) //[Function: Person]
Person.prototype = {
name:“小红”
}
Object.defineProperty(Person.prototype,“constructor”,{
enumerable: false,
value: Person
})
var p2 = new Person()
console.log(p2.constructor) //[Function: Person]
// 实现原型链的一种基本模式
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
// 继承,用 SuperType 类型的一个实例来重写 SubType 类型的原型对象
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); // true
其中,SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋值给 SubType 的原型实现的。
实现的本质是重写子类型的原型对象,代之以一个新类型的实例。子类型的新原型对象中有一个内部属性 Prototype
指向了 SuperType 的原型,还有一个从 SuperType 原型中继承过来的属性 constructor 指向了 SuperType 构造函数。
最终的原型链是这样的:instance 指向 SubType 的原型,SubType 的原型又指向 SuperType 的原型,SuperType 的原型又指向 Object 的原型(所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype)
原型链继承的缺点:
1、在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性,并且会被所有的实例共享。这样理解:在超类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型实例所共享
2、在创建子类型的实例时,不能向超类型的构造函数中传递参数
有,就是借用构造函数继承
借用构造函数继承(也称伪造对象或经典继承)
// 在子类型构造函数的内部调用超类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上
function SuperType(){
// 定义引用类型值属性
this.colors = [“red”,“green”,“blue”];
}
function SubType(){
// 继承 SuperType,在这里还可以给超类型构造函数传参
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push(“purple”);
alert(instance1.colors); // “red,green,blue,purple”
var instance2 = new SubType();
alert(instance2.colors); // “red,green,blue”
通过使用 apply() 或 call() 方法,我们实际上是在将要创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType() 函数中定义的所有对象初始化代码。结果 SubType 的每个实例就都会具有自己的 colors 属性的副本了
。
借用构造函数的优点是解决了原型链实现继承存在的两个问题。
但是一波已平,一波又起
借用构造函数的缺点是方法都在构造函数中定义,因此函数复用就无法实现了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
既然两种方法都没有对方的缺点,那就可以把两者方法结合起来,就解决了,这种方法叫做组合继承
,
组合继承(也称伪经典继承)
将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。
function SuperType(name){
this.name = name;
this.colors = [“red”,“green”,“blue”];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
// 借用构造函数方式继承属性
SuperType.call(this,name);
this.age = age;
}
// 原型链方式继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType(“luochen”,22);
instance1.colors.push(“purple”);
alert(instance1.colors); // “red,green,blue,purple”
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType(“tom”,34);
alert(instance2.colors); // “red,green,blue”
instance2.sayName();
instance2.sayAge();
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 javascript 中最常用的继承模式。而且,使用 instanceof 操作符和 isPrototype() 方法也能够用于识别基于组合继承创建的对象。
但它也有自己的不足 – 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
自定义一个函数来实现原型式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在 object() 函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。实质上,object() 对传入其中的对象执行了一次浅复制。
其实这个方法就是Object.create的简单实现
直接用Object.create
实现原型式继承
这个方法接收两个参数:一是用作新对象原型的对象和一个为新对象定义额外属性的对象。在传入一个参数的情况下,此方法与 object() 方法作用一致。在传入第二个参数的情况下,指定的任何属性都会覆盖原型对象上的同名属性。
var person = {
name: “luochen”,
colors: [“red”,“green”,“blue”]
};
var anotherPerson1 = Object.create(person,{
name: {
value: “tom”
}
});
var anotherPerson2 = Object.create(person,{
name: {
value: “jerry”
}
});
anotherPerson1.colors.push(“purple”);
alert(anotherPerson1.name); // “tom”
alert(anotherPerson2.name); // “jerry”
alert(anotherPerson1.colors); // “red,green,blue,purple”
alert(anotherPerson2.colors); // “red,green,bule,purple”;
只是想让一个对象与另一个对象类似的情况下,原型式继承是完全可以胜任的。但是缺点是:包含引用类型值的属性始终都会共享相应的值
,这也是原型链继承的缺点
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象
function createPerson(original){
var clone = Object.create(original); // 通过 Object.create() 函数创建一个新对象
clone.sayGood = function(){ // 增强这个对象
alert(“hello world!!!”);
};
return clone; // 返回这个对象
}
这个方式跟工厂模式生产对象很类似。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。此模式的缺点是做不到函数复用
寄生组合式继承
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
function SuperType(name){
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
// 创建超类型原型的一个副本
var anotherPrototype = Object.create(SuperType.prototype);
// 重设因重写原型而失去的默认的 constructor 属性
anotherPrototype.constructor = SubType;
// 将新创建的对象赋值给子类型的原型
SubType.prototype = anotherPrototype;
这个例子的高效率体现在它只调用一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上面创建不必要,多余的属性。与此同时,原型链还能保持不变;因此还能够正常使用 instance 操作符和 isPrototype() 方法
===============================================================================
第一问:安全类型检测——typeof和instanceof 区别以及缺陷,以及解决方案
这两个方法都可以用来判断变量类型
区别:前者是判断这个变量是什么类型,后者是判断这个变量是不是某种类型,返回的是布尔值
(1)typeof
缺陷:
1.不能判断变量具体的数据类型比如数组、正则、日期、对象,因为都会返回object,不过可以判断function,如果检测对象是正则表达式的时候,在Safari和Chrome中使用typeof的时候会错误的返回"function",其他的浏览器返回的是object.
2.判断null的时候返回的是一个object,这是js的一个缺陷,判断NaN的时候返回是number
(2)instanceof
可以用来检测这个变量是否为某种类型,返回的是布尔值,并且可以判断这个变量是否为某个函数的实例,它检测的是对象的原型
let num = 1
num instanceof Number // false
num = new Number(1)
num instanceof Number // true
明明都是num,而且都是1,只是因为第一个不是对象,是基本类型,所以直接返回false,而第二个是封装成对象,所以true。
这里要严格注意这个问题,有些说法是检测目标的__proto__与构造函数的prototype相同即返回true,这是不严谨的,检测的一定要是对象才行,如:
let num = 1
num.proto === Number.prototype // true
num instanceof Number // false
num = new Number(1)
num.proto === Number.prototype // true
num instanceof Number // true
num.proto === (new Number(1)).proto // true
此外,instanceof还有另外一个缺点:如果一个页面上有多个框架,即有多个全局环境,那么我在a框架里定义一个Array,然后在b框架里去用instanceof去判断,那么该array的原型链上不可能找到b框架里的array,则会判断该array不是一个array。
解决方案:使用Object.prototype.toString.call(value) 方法去调用对象,得到对象的构造函数名。可以解决instanceof的跨框架问题,缺点是对用户自定义的类型,它只会返回[object Object]
第二问:既然提到了instanceof,那手写实现下instanceof吧
// [1,2,3] instanceof Array ---- true
// L instanceof R
// 变量R的原型 存在于 变量L的原型链上
function instance_of(L,R){
// 验证如果为基本数据类型,就直接返回false
const baseType = [‘string’, ‘number’,‘boolean’,‘undefined’,‘symbol’]
if(baseType.includes(typeof(L))) { return false }
let RP = R.prototype; //取 R 的显示原型
L = L.proto; //取 L 的隐式原型
while(true){ // 无线循环的写法(也可以使 for(;😉 )
if(L === null){ //找到最顶层
return false;
}
if(L === RP){ //严格相等
return true;
}
L = L.proto; //没找到继续向上一层原型链查找
}
}
第三问:作用域安全的构造函数–当我们new一个构造函数的时候可以获得一个实例,要是我们忘记写new了呢?
例如
function Person(){
this.name = “小红”
}
p = Person();
这会发生什么问题?,怎么解决
这样直接使用,this会映射到全局对象window上。解决方法可以是:首先确认this对象是正确类型的实例。如果不是,那么会创建新的实例并返回。请看下面的例子
function Person(){
if(this instanceof Person){
this.name = “小红”
}else{
return new Person()
}
}
p = Person();
在JavaScript代码中,由于浏览器之间行为的差异,多数JavaScript代码包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。例如添加事件的函数:
function addEvent (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent(“on” + type, handler);
} else {
element[“on” + type] = handler;
}
}
每次调用addEvent()的时候,都要对浏览器所支持的能力仔细检查。首先检查是否支持addEventListener方法,如果不支持再检查是否支持attachEvent方法,如果还不支持,就用DOM 0级的方法添加事件。在调用addEvent()过程中,每次这个过程都要走一遍。其实,浏览器支持其中的一种方法就会一直支持他,就没有必要再进行其他分支的检测了,也就是说if语句不必每次都执行,代码可以运行的更快一些。解决的方案称之为惰性载入。
所谓惰性载入,就是说函数的if分支只会执行一次,之后调用函数时,直接进入所支持的分支代码。有两种实现惰性载入的方式,第一种事函数在第一次调用时,对函数本身进行二次处理,该函数会被覆盖为符合分支条件的函数,这样对原函数的调用就不用再经过执行的分支了,我们可以用下面的方式使用惰性载入重写addEvent()。
function addEvent (type, element, handler) {
if (element.addEventListener) {
addEvent = function (type, element, handler) {
element.addEventListener(type, handler, false);
}
}
else if(element.attachEvent){
addEvent = function (type, element, handler) {
element.attachEvent(‘on’ + type, handler);
}
}
else{
addEvent = function (type, element, handler) {
element[‘on’ + type] = handler;
}
}
return addEvent(type, element, handler);
}
在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()的时候,便会直接调用新赋值的函数,这样就不用再执行if语句了。
第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能。一下就是按照这一思路重写的addEvent()。
var addEvent = (function () {
if (document.addEventListener) {
return function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent(‘on’ + type, fun);
}
}
else {
return function (type, element, fun) {
element[‘on’ + type] = fun;
}
}
})();
这个例子中使用的技巧是创建一个匿名的自执行函数,通过不同的分支以确定应该使用那个函数实现,实际的逻辑都一样,不一样的地方就是使用了函数表达式(使用了var定义函数)和新增了一个匿名函数,另外每个分支都返回一个正确的函数,并立即将其赋值给变量addEvent。
惰性载入函数的优点只执行一次if分支,避免了函数每次执行时候都要执行if分支和不必要的代码,因此提升了代码性能,至于那种方式更合适,就要看您的需求而定了。
概念:限制一个函数在一定时间内只能执行一次。
主要实现思路 就是通过 setTimeout 定时器,通过设置延时时间,在第一次调用时,创建定时器,先设定一个变量true,写入需要执行的函数。第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为false。那么下次判断变量时则为false,函数会依次运行。目的在于在一定的时间内,保证多次函数的请求只执行最后一次调用。
函数节流的代码实现
function throttle(fn,wait){
var timer = null;
return function(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(context,args);
timer = null;
},wait)
}
}
}
function handle(){
console.log(Math.random());
}
window.addEventListener(“mousemove”,throttle(handle,1000));
函数节流的应用场景(throttle)
-
DOM 元素的拖拽功能实现(mousemove)
-
高频点击提交,表单重复提交
-
搜索联想(keyup)
-
计算鼠标移动的距离(mousemove)
-
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
-
射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
-
监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,- - 才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次.
概念:函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。
函数防抖的要点,是需要一个 setTimeout 来辅助实现,延迟运行需要执行的代码。如果方法多次触发,则把上次记录的延迟执行代码用 clearTimeout 清掉,重新开始计时。若计时期间事件没有被重新触发,等延迟时间计时完毕,则执行目标代码。
函数防抖的代码实现
function debounce(fn,wait){
var timer = null;
return function(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(fn,wait);
}
}
function handle(){
console.log(Math.random());
}
window.addEventListener(“resize”,debounce(handle,1000));
函数防抖的使用场景
函数防抖一般用在什么情况之下呢?一般用在,连续的事件只需触发一次回调的场合。具体有:
-
搜索框搜索输入。只需用户最后一次输入完,再发送请求;
-
用户名、手机号、邮箱输入验证;
-
浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。
目前遇到过的用处就是这些,理解了原理与实现思路,小伙伴可以把它运用在任何需要的场合,提高代码质量。
动画原理 :
眼前所看到图像正在以每秒60次的频率刷新,由于刷新频率很高,因此你感觉不到它在刷新。而动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢?
刷新频率为60Hz的屏幕每16.7ms刷新一次,我们在屏幕每次刷新前,将图像的位置向左移动一个像素,即1px。这样一来,屏幕每次刷出来的图像位置都比前一个要差1px,因此你会看到图像在移动;由于我们人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此你才会看到图像在流畅的移动,这就是视觉效果上形成的动画。
与setTimeout相比较:
理解了上面的概念以后,我们不难发现,setTimeout 其实就是通过设置一个间隔时间来不断的改变图像的位置,从而达到动画效果的。但我们会发现,利用seTimeout实现的动画在某些低端机上会出现卡顿、抖动的现象。 这种现象的产生有两个原因:
-
setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。
-
刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象
requestAnimationFrame:与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
除此之外,requestAnimationFrame还有以下两个优势:
-
CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
-
函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。
白屏时间:
白屏时间指的是浏览器开始显示内容的时间。因此我们只需要知道是浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。
计算白屏时间
因此,我们通常认为浏览器开始渲染 标签或者解析完 标签的时刻就是页面白屏结束的时间点。
因此白屏时间则可以这样计算出:
可使用 Performance API 时
:
白屏时间 = firstPaint - performance.timing.navigationStart;
不可使用 Performance API 时
:
白屏时间 = firstPaint - pageStartTime; //虽然我们知道这并不准确,毕竟DNS解析,tcp三次握手等都没计算入内。
首屏时间:
首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间。对于用户体验来说,首屏时间是用户对一个网站的重要体验因素。通常一个网站,如果首屏时间在5秒以内是比较优秀的,10秒以内是可以接受的,10秒以上就不可容忍了。超过10秒的首屏时间用户会选择刷新页面或立刻离开。
通常计算首屏的方法有
-
首屏模块标签标记法
-
统计首屏内加载最慢的图片的时间
-
自定义首屏内容计算法
1、首屏模块标签标记法
首屏模块标签标记法,通常适用于首屏内容不需要通过拉取数据才能生存以及页面不考虑图片等资源加载的情况。我们会在 HTML 文档中对应首屏内容的标签结束位置,使用内联的 JavaScript 代码记录当前时间戳。如下所示:
此时首屏时间等于 firstScreen - performance.timing.navigationStart;
事实上首屏模块标签标记法 在业务中的情况比较少,大多数页面都需要通过接口拉取数据才能完整展示,因此我们会使用 JavaScript 脚本来判断首屏页面内容加载情况。
2、统计首屏内图片完成加载的时间
通常我们首屏内容加载最慢的就是图片资源,因此我们会把首屏内加载最慢的图片的时间当做首屏的时间。
由于浏览器对每个页面的 TCP 连接数有限制,使得并不是所有图片都能立刻开始下载和显示。因此我们在 DOM树 构建完成后将会去遍历首屏内的所有图片标签,并且监听所有图片标签 onload 事件,最终遍历图片标签的加载时间的最大值,并用这个最大值减去 navigationStart 即可获得近似的首屏时间。
此时首屏时间等于 加载最慢的图片的时间点 - performance.timing.navigationStart;
//首屏时间尝试:
//1,获取首屏基线高度
//2,计算出基线dom元素之上的所有图片元素
//3,所有图片onload之后为首屏显示时间
//
function getOffsetTop(ele) {
var offsetTop = ele.offsetTop;
if (ele.offsetParent !== null) {
offsetTop += getOffsetTop(ele.offsetParent);
}
return offsetTop;
}
var firstScreenHeight = win.screen.height;
var firstScreenImgs = [];
var isFindLastImg = false;
var allImgLoaded = false;
var t = setInterval(function() {
var i, img;
if (isFindLastImg) {
if (firstScreenImgs.length) {
for (i = 0; i < firstScreenImgs.length; i++) {
img = firstScreenImgs[i];
if (!img.complete) {
allImgLoaded = false;
break;
} else {
allImgLoaded = true;
}
}
} else {
allImgLoaded = true;
}
if (allImgLoaded) {
collect.add({
firstScreenLoaded: startTime - Date.now()
});
clearInterval(t);
}
} else {
var imgs = body.querySelector(‘img’);
for (i = 0; i<imgs.length; i++) {
img = imgs[i];
var imgOffsetTop = getOffsetTop(img);
if (imgOffsetTop > firstScreenHeight) {
isFindLastImg = true;
break;
} else if (imgOffsetTop <= firstScreenHeight && !img.hasPushed) {
img.hasPushed = 1;
firstScreenImgs.push(img);
}
}
}
}, 0);
doc.addEventListener(‘DOMContentLoaded’, function() {
var imgs = body.querySelector(‘img’);
if (!imgs.length) {
isFindLastImg = true;
}
});
win.addEventListener(‘load’, function() {
allImgLoaded = true;
isFindLastImg = true;
if (t) {
clearInterval(t);
}
collect.log(collect.global);
});
解释一下思路,大概就是判断首屏有没有图片,如果没图片就用domready时间,如果有图,分2种情况,图在首屏,图不在首屏,如果在则收集,并判断加载状态,加载完毕之后则首屏完成加载,如果首屏没图,找到首屏下面的图,立刻触发首屏完毕。可以想象这么做前端收集是不准的,但是可以确保最晚不会超过win load,所以应该还算有些意义。。没办法,移动端很多浏览器不支持performance api,所以土办法前端收集,想出这么个黑魔法,在基线插入节点收集也是个办法,但是不友好,而且现在手机屏幕这么多。。
3、自定义模块内容计算法
由于统计首屏内图片完成加载的时间比较复杂。因此我们在业务中通常会通过自定义模块内容,来简化计算首屏时间。如下面的做法:
-
忽略图片等资源加载情况,只考虑页面主要 DOM
-
只考虑首屏的主要模块,而不是严格意义首屏线以上的所有内容
实际上用performance.timing来计算首屏加载时间与白屏时间非常简单与精确。不过目前只支持IE10和chrome
贴下其API的使用
var navigationStart = performance.timing.navigationStart;
//1488984540668
console.log(navigationStart);
//Wed Mar 08 2017 22:49:44 GMT+0800 (中国标准时间)
console.log(new Date(new Date(navigationStart)));
复制代码
redirectStart:到当前页面的重定向开始的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为0
redirectEnd:到当前页面的重定向结束的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为0
console.log(performance.timing.redirectStart);//0
console.log(performance.timing.redirectEnd);//0
fetchStart:开始通过HTTP GET取得页面的时间
console.log(performance.timing.fetchStart);//1488984540668
domainLookupStart:开始査询当前页面DNS的时间,如果使用了本地缓存或持久连接,则与fetchStart值相等
domainLookupEnd:査询当前页面DNS结束的时间,如果使用了本地缓存或持久连接,则与fetchStart值相等
console.log(performance.timing.domainLookupStart);//1488984540670
console.log(performance.timing.domainLookupEnd);//1488984540671
connectStart:浏览器尝试连接服务器的时间
secureConnectionStart:浏览器尝试以SSL方式连接服务器的时间。不使用SSL方式连接时,这个属性的值为0
connectEnd:浏览器成功连接到服务器的时间
console.log(performance.timing.connectStart);//1488984540671
console.log(performance.timing.secureConnectionStart);//0
console.log(performance.timing.connectEnd);//1488984540719
requestStart:浏览器开始请求页面的时间
responseStart:浏览器接收到页面第一字节的时间
responseEnd:浏览器接收到页面所有内容的时间
console.log(performance.timing.requestStart);//1488984540720
console.log(performance.timing.responseStart);//1488984540901
console.log(performance.timing.responseEnd);//1488984540902
unloadEventStart:前一个页面的unload事件开始的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为0
unloadEventEnd:前一个页面的unload事件结束的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为0
console.log(performance.timing.unloadEventStart);//1488984540902
console.log(performance.timing.unloadEventEnd);//1488984540903
domLoading:document.readyState变为"loading"的时间,即开始解析DOM树的时间
domInteractive:document.readyState变为"interactive"的时间,即完成完成解析DOM树的时间
domContentLoadedEventStart:发生DOMContentloaded事件的时间,即开始加载网页内资源的时间
domContentLoadedEventEnd:DOMContentLoaded事件已经发生且执行完所有事件处理程序的时间,网页内资源加载完成的时间
domComplete:document.readyState变为"complete"的时间,即DOM树解析完成、网页内资源准备就绪的时间
console.log(performance.timing.domLoading);//1488984540905
console.log(performance.timing.domInteractive);//1488984540932
console.log(performance.timing.domContentLoadedEventStart);//1488984540932
console.log(performance.timing.domContentLoadedEventEnd);//1488984540932
console.log(performance.timing.domComplete);//1488984540932
loadEventStart:发生load事件的时间,也就是load回调函数开始执行的时间
loadEventEnd:load事件已经发生且执行完所有事件处理程序的时间
console.log(performance.timing.loadEventStart);//1488984540933
console.log(performance.timing.loadEventEnd);//1488984540933
多线程技术在服务端技术中已经发展的很成熟了,而在Web端的应用中却一直是鸡肋
在新的标准中,提供的新的WebWork API,让前端的异步工作变得异常简单。
使用:创建一个Worker对象,指向一个js文件,然后通过Worker对象往js文件发送消息,js文件内部的处理逻辑,处理完毕后,再发送消息回到当前页面,纯异步方式,不影响当前主页面渲染。
work.js
onmessage = function (event) {
//从1加到num
var num = event.data;
var result = 0;
for (var i = 1; i <= num; i++) {
result += i;
}
postMessage(result);
}
==================================================================================
-
了解Promise,Promise是一种异步编程的解决方案,有三种状态,pending(进行中)、resolved(已完成)、rejected(已失败)。当Promise的状态由pending转变为resolved或reject时,会执行相应的方法
-
Promised的特点是只有异步操作的结果,可以决定当前是哪一种状态,任务其他操作都无法改变这个状态,也是“Promise”的名称的由来,同时,状态一旦改变,就无法再次改变状态
Promise解决的痛点:
1)回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象,是为解决异步操作函数里的嵌套回调(callback hell)问题,代码臃肿,可读性差,只能在回调里处理异常
2)promise可以支持多个并发的请求,获取并发请求中的数据
3)promise可以解决可读性的问题,异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的,这样的代码读起来会非常吃力
4)promise可以解决信任问题,对于回调过早、回调过晚或没有调用和回调次数太少或太多,由于promise只能决议一次,决议值只能有一个,决议之后无法改变,任何then中的回调也只会被调用一次,所以这就保证了Promise可以解决信任问题
5)Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了
第三问:Promise 解决的痛点还有其他方法可以解决吗?如果有,请列举。
1)Promise 解决的痛点还有其他方法可以解决,比如setTimeout、事件监听、回调函数、Generator函数,async/await
2)setTimeout:缺点不精确,只是确保在一定时间后加入到任务队列,并不保证立马执行。只有执行引擎栈中的代码执行完毕,主线程才会去读取任务队列
3)事件监听:任务的执行不取决于代码的顺序,而取决于某个事件是否发生
4)Generator函数虽然将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。即如何实现自动化的流程管理
5)async/await
1)创造一个Promise实例
2)Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
3)可用Promise的try和catch方法预防异常
1) promise可以支持多个并发的请求,获取并发请求中的数据
2)promise可以解决可读性的问题,异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的,这样的代码读起来会非常吃力
promise的问题为:
promise一旦执行,无法中途取消
promise的错误无法在外部被捕捉到,只能在内部进行预判处理
promise的内如何执行,监测起来很难
解决办法
正是因为这些原因,ES7引入了更加灵活多变的async,await来处理异步
果辛辛苦苦写完代码,测试后发现不兼容IE6、7增么办?难道要推翻用回调函数重写?当然不是这样,轮子早就造好了。
我们可以使用es6-promise-polyfill。es6-promise-polyfill可以使用页面标签直接引入,可以通过es6的import方法引入(如果你是用webpack),在node中可以使用require引入,也可以在Seajs中作为依赖引入。
引入这个polyfill之后,它会在window对象中加入Promise对象。这样我们就可以全局使用Promise了。
第八问:怎么让一个函数无论promise对象成功和失败都能被调用?
笨方法:
在两个回调中分别执行一次函数。
推荐方式:
扩展一个 Promise.finally(),finally方法用于指定不管Promise对象最后状态如何,都会执行的操作,它与done方法的最大区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
//添加finally方法
Promise.prototype.finally=function (callback) {
var p=this.constructor;
return this.then(//只要是promise对象就可以调用then方法
value => p.resolve(callback()).then(() => value),
reason => p.resolve(callback()).then(() => {throw reason})
);
}
对finally方法的理解:
(1)p.resolve(callback())这句函数callback已经执行
(2)finally方法return的是一个promise对象,所以还可以继续链式调用其他方法
(3)对于Promise.resolve方法
Promise.resolve(‘foo’);
等价于
new Promise(resolve => resolve(‘foo’));
所以可以通过then方法的回调函数 接受 实例对象返回的参数
比如:
Promise.resolve(function(){console.log(2);}).then(function(cb){cb()})
第九问:红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)三个亮灯函数已经存在:
function red() {
console.log(‘red’);
}
function green() {
console.log(‘green’);
}
function yellow() {
console.log(‘yellow’);
}
解析
红灯3秒亮一次,绿灯1秒亮一次 ,黄灯2秒亮一次,意思就是3秒执行一次red函数,2秒执行一次green函数,1秒执行一次yellow函数,不断交替重复亮灯,意思就是按照这个顺序一直执行这3个函数,这步可以利用递归来实现。
答案:
function red() {
console.log(‘red’);
}
function green() {
console.log(‘green’);
}
function yellow() {
console.log(‘yellow’);
}
var light = function (timmer, cb) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
cb();
resolve();
}, timmer);
});
};
var step = function () {
Promise.resolve().then(function () {
return light(3000, red);
}).then(function () {
return light(2000, green);
}).then(function () {
return light(1000, yellow);
}).then(function () {
step();
});
}
step();
第十问:实现 mergePromise 函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组 data 中。
const timeout = ms => new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});
const ajax1 = () => timeout(2000).then(() => {
console.log(‘1’);
return 1;
});
const ajax2 = () => timeout(1000).then(() => {
console.log(‘2’);
return 2;
});
const ajax3 = () => timeout(2000).then(() => {
console.log(‘3’);
return 3;
});
const mergePromise = ajaxArray => {
// 在这里实现你的代码
};
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log(‘done’);
console.log(data); // data 为 [1, 2, 3]
});
// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
解析
首先 ajax1 、ajax2、ajax3 都是函数,只是这些函数执行后会返回一个 Promise,按题目的要求我们只要顺序执行这三个函数就好了,然后把结果放到 data 中,但是这些函数里都是异步操作,想要按顺序执行,然后输出 1,2,3并没有那么简单,看个例子。
function A() {
setTimeout(function () {
console.log(‘a’);
}, 3000);
}
function B() {
setTimeout(function () {
console.log(‘b’);
}, 1000);
}
A();
B();
// b
// a
答案
// 保存数组中的函数执行后的结果
var data = [];
// Promise.resolve方法调用时不带参数,直接返回一个resolved状态的 Promise 对象。
var sequence = Promise.resolve();
ajaxArray.forEach(function (item) {
// 第一次的 then 方法用来执行数组中的每个函数,
// 第二次的 then 方法接受数组中的函数执行后返回的结果,
// 并把结果添加到 data 中,然后把 data 返回。
// 这里对 sequence 的重新赋值,其实是相当于延长了 Promise 链
sequence = sequence.then(item).then(function (res) {
data.push(res);
return data;
});
})
// 遍历结束后,返回一个 Promise,也就是 sequence, 他的 [[PromiseValue]] 值就是 data,
// 而 data(保存数组中的函数执行后的结果) 也会作为参数,传入下次调用的 then 方法中。
return sequence;
function loadImageAsync(url) {
return new Promise(function(resolve,reject) {
var image = new Image();
image.onload = function() {
resolve(image)
};
image.onerror = function() {
reject(new Error(‘Could not load image at’ + url));
};
image.src = url;
});
}
请看我的另一篇文章
=============================================================================
目前共有三种事件模型,它们分别是:
DOM0 级事件模型、IE 事件模型、DOM2 级事件模型
DOM0 级事件模型 又称原始事件模型,有两种方式,最直观的提下如下代码:
// 方式一
// 将事件直接通过属性绑定在元素上
// 方式二
// 获取到页面元素后,通过 onclick 等事件,将触发的方法指定为元素的事件
// 取消该事件可直接设置为 null
var btn = document.getElementById(‘btn’)
btn.onclick = function () {…}
btn.onclick = null
DOM0 级的事件模型,方法较为简单,但是将逻辑和界面耦合在了一起,对之后的维护不是很友好
但也不是没有优点,这种方式兼容所有浏览器
IE 事件模型
IE 事件模型一共有两个阶段:
-
事件处理阶段:事件在达到目标元素时,触发监听事件
-
事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行
绑定和移除事件的 api 分别如下:
// 绑定事件
el.attachEvent(eventType, handler)
// 移除事件
el.detachEvent(eventType, handler)
参数说明:
eventType 是如onclick一样的带有”on“的事件,绑定事件时,handler可以是具名函数,也可以是匿名函数,但是匿名函数无法移除
我们会发现,IE 事件模型与我们平时用的事件绑定方法addEventListener,也就是下面要说的 DOM2 级事件模型有点相似,但是 IE 事件模型仅在 IE 浏览器中有效,不兼容其他浏览器
DOM2 级事件模型
W3C标准模型,也是我们频繁使用的事件模型,除 IE6-8 以外的所有现代浏览器都支持该事件模型
DOM2 级事件模型共有三个阶段:
-
事件捕获阶段:事件从 document 向下传播到目标元素,依次检查所有节点是否绑定了监听事件,如果有则执行
-
事件处理阶段:事件在达到目标元素时,触发监听事件
-
事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行
- Dom0 模型:
-
this指向: 指向函数中的this指向的是被绑定的元素
-
绑定多个同事件类型的事件时,如对同个元素绑定多个click,则后面的会覆盖前面的,最后只有一个会执行
- IE 模型:
-
this指向: 指向函数中的this指向的是window
-
绑定多个同事件类型的事件时,如对同个元素绑定多个click,则后面的不会覆盖前面的,执行顺序是先执行下面的,从下往上执行
-
只有两个参数:第一个参数为 事件类型,第二个为事件执行函数目标阶段,
-
只有事件冒泡阶段
-
获取目标元素: window.event.srcElement
- Dom2 模型:
-
this指向: 指向函数中的this指向的是被绑定的元素
-
绑定多个同事件类型的事件时,如对同个元素绑定多个click,则后面的不会覆盖前面的,执行顺序是先执行上面的,从上往下执行
-
有三个参数:第一个参数为 事件类型,第二个为事件执行函数,第三个为布尔值,表示是否用事件捕获
-
有事件捕获阶段,处于目标阶段,事件冒泡阶段
-
获取目标元素: event.target
欢迎补充。。。
事件流所描述的就是从页面中接受事件的顺序,事件流分为两种:事件冒泡(主流)和事件捕获. 版本IE(IE8及以下版本)不支持捕获阶段
1、事件冒泡
事件开始时由具体元素接收,然后逐级向上传播到父元素
2、事件捕获
父元素的节点更早接收事件,而具体元素最后接收事件,与事件冒泡相反
- 阻止事件冒泡 :使用
e.stopPropagation();
IE使用window.event.cancelBubble = true;
function stopBubble(e){
if(e&&e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}
}
-
阻止事件捕获:与冒泡一样 使用`e.stopPropagation(),IE没有捕获阶段,所以不用
-
阻止默认事件:
function stopDefault(e){
if(e&&e.preventDefault()){
e.preventDefault();
}else{
windown.event.returnValue = false
}
}
第六问:事件的委托(代理 Delegated Events)的原理以及优缺点
委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会触发。这是靠事件的冒泡机制来实现的,
- 优点是:
-
(1)可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
-
(2)可以实现当新增子对象时无需再次对其绑定事件,对于动态内容部分尤为合适
- 缺点是:
- 事件代理的应用常用应该仅限于上述需求下,如果把所有事件都用代理就可能会出现事件误判,即本不应用触发事件的被绑上了事件。
例子:
要求:兼容浏览器 考点:事件对象e,IE下事件对应的属性名。 重点是要说到target,currentTarget以及IE下的srcElement和this。
第七问:编写一个自定义事件类,包含on/off/emit/once方法
可能谈到Evnet,customerEvent,document.createEvent
在工作过程中,经常会遇到按需加载的需求,即在脚本加载完成后,返回一个回调函数,在回调函数中进行相关操作,那如何去判断脚本是否加载完成了呢?
可以对加载的js对象使用onload来判断,jsDom.onload
ie6、7不支持js.onload方法,使用js.onreadystatechange来解决
js.onreadystatechange来跟踪每个状态的变化(loading、loaded、interactive、complete),当返回状态为loaded或者complete时,表示加载完成,返回回调函数.
JavaScript
-
js的基本类型有哪些?引用类型有哪些?null和undefined的区别。
-
如何判断一个变量是Array类型?如何判断一个变量是Number类型?(都不止一种)
-
Object是引用类型嘛?引用类型和基本类型有什么区别?哪个是存在堆哪一个是存在栈上面的?
-
JS常见的dom操作api
-
解释一下事件冒泡和事件捕获
-
事件委托(手写例子),事件冒泡和捕获,如何阻止冒泡?如何组织默认事件?
-
对闭包的理解?什么时候构成闭包?闭包的实现方法?闭包的优缺点?
-
this有哪些使用场景?跟C,Java中的this有什么区别?如何改变this的值?
-
call,apply,bind
-
显示原型和隐式原型,手绘原型链,原型链是什么?为什么要有原型链
-
创建对象的多种方式
-
实现继承的多种方式和优缺点
-
new 一个对象具体做了什么
-
手写Ajax,XMLHttpRequest
-
变量提升
-
举例说明一个匿名函数的典型用例
-
指出JS的宿主对象和原生对象的区别,为什么扩展JS内置对象不是好的做法?有哪些内置对象和内置函数?
-
attribute和property的区别
-
document load和document DOMContentLoaded两个事件的区别
-
JS代码调试
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
opPropagation(),IE没有捕获阶段,所以不用
- 阻止默认事件:
function stopDefault(e){
if(e&&e.preventDefault()){
e.preventDefault();
}else{
windown.event.returnValue = false
}
}
第六问:事件的委托(代理 Delegated Events)的原理以及优缺点
委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会触发。这是靠事件的冒泡机制来实现的,
- 优点是:
-
(1)可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
-
(2)可以实现当新增子对象时无需再次对其绑定事件,对于动态内容部分尤为合适
- 缺点是:
- 事件代理的应用常用应该仅限于上述需求下,如果把所有事件都用代理就可能会出现事件误判,即本不应用触发事件的被绑上了事件。
例子:
要求:兼容浏览器 考点:事件对象e,IE下事件对应的属性名。 重点是要说到target,currentTarget以及IE下的srcElement和this。
第七问:编写一个自定义事件类,包含on/off/emit/once方法
可能谈到Evnet,customerEvent,document.createEvent
在工作过程中,经常会遇到按需加载的需求,即在脚本加载完成后,返回一个回调函数,在回调函数中进行相关操作,那如何去判断脚本是否加载完成了呢?
可以对加载的js对象使用onload来判断,jsDom.onload
ie6、7不支持js.onload方法,使用js.onreadystatechange来解决
js.onreadystatechange来跟踪每个状态的变化(loading、loaded、interactive、complete),当返回状态为loaded或者complete时,表示加载完成,返回回调函数.
JavaScript
-
js的基本类型有哪些?引用类型有哪些?null和undefined的区别。
-
如何判断一个变量是Array类型?如何判断一个变量是Number类型?(都不止一种)
-
Object是引用类型嘛?引用类型和基本类型有什么区别?哪个是存在堆哪一个是存在栈上面的?
-
JS常见的dom操作api
-
解释一下事件冒泡和事件捕获
-
事件委托(手写例子),事件冒泡和捕获,如何阻止冒泡?如何组织默认事件?
-
对闭包的理解?什么时候构成闭包?闭包的实现方法?闭包的优缺点?
-
this有哪些使用场景?跟C,Java中的this有什么区别?如何改变this的值?
-
call,apply,bind
-
显示原型和隐式原型,手绘原型链,原型链是什么?为什么要有原型链
-
创建对象的多种方式
-
实现继承的多种方式和优缺点
-
new 一个对象具体做了什么
-
手写Ajax,XMLHttpRequest
-
变量提升
-
举例说明一个匿名函数的典型用例
-
指出JS的宿主对象和原生对象的区别,为什么扩展JS内置对象不是好的做法?有哪些内置对象和内置函数?
-
attribute和property的区别
-
document load和document DOMContentLoaded两个事件的区别
-
JS代码调试
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-4u5H278p-1713066690998)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!