1.什么是闭包
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
js闭包的6种应用场景!!!这下会用了 - 掘金 (juejin.cn)
详见JavaScript面试之闭包(详细总结)_js面试闭包完美回答-CSDN博客
// 节流函数封装
function throttle(func, delay) {
let timer = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, arguments);
timer = null;
}, delay);
}
};
}
// 防抖函数封装
function debounce(func, delay) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
2.什么是构造函数
在js中,任何用
new
关键字来调用的函数,都叫做构造函数,一般
首字母大写。(内置构造函数Object、Array、String、Boolean、Number、Date等等)
js new函数的步骤
在JavaScript中,使用new关键字来创建一个对象实例时,会经历以下步骤:
-
创建一个空对象。
-
将这个新对象的原型链接到构造函数的
**prototype**属性上,以便新对象可以访问构造函数原型中的属性和方法。 -
将构造函数的作用域(
**this**)绑定到新对象上,使构造函数内部的属性和方法可以操作新对象。 -
执行构造函数中的代码,初始化新对象的属性。
-
如果构造函数返回了一个对象,则返回该对象;否则,返回新创建的对象。
function Person(name, age) { this.name = name; this.age = age; }
Person.prototype.sayHello = function() { console.log("Hello, my name is " + this.name); };
var john = new Person("John", 30); john.sayHello(); // 输出:Hello, my name is John
3.构造函数为什么要用new关键字调用?
使用new关键字调用this对象指向构造函数生成的对象实例* 四、构造函数的返回值? 1、没有return,默认返回this - 具体原因见目录三:执行过程
function Person1 (name) { this.name = name }
var p1 = new Person1('Tom') console.log(p1) // Person1 {name: 'Tom'}
2、return 基本数据类型,最终返回this
function Person2 (name) { this.name = name return 'zhangsan' }
var p2 = new Person2('Jack') console.log(p2) // Person2 {name: 'Jack'}
3、return 复杂数据类型(对象),返回该that对象,this对象被丢失
function Person3 (name) { this.name = name var that = [1, 2, 3] return that }
var p3 = new Person3('Jack') console.log(p3) // [1, 2, 3]
/* 构造函数 */
function Person (name, age) {
this.name = name // 属性、方法前必须加this,this表示当前运行时的对象
this.age = age
this.say = function (word) {
console.log('我是人')
return word
}
}
/* 实例对象 */
var per = new Person('Jack', 16)
console.log(per) // Person {name: 'Jack', age: 10, say: f}
console.log(per.constructor) // f Person() {} // 构造函数原型
console.log(per.name) // Jack
console.log(per.age) // 10
console.log(per.say('我是高中生')) // 我是高中生
4.创建函数的几种方式
1.函数声明
function sum1(num1,num2) {
return num1+num2
}
2.函数表达式
let sum2 = function(num1,num2) {
return num1+num2
}
3.函数对象方式
let sum3 = new Function('num1','num2','return num1+num2')
5.创建对象的几种方式
JavaScript对象与创建对象的方式 - 掘金 (juejin.cn)
JavaScript中常用的创建对象的几种方式包括:
-
对象字面量:使用对象字面量直接创建对象。
let person = { name: 'Alice', age: 30 }; -
构造函数:通过构造函数创建对象。
function Person(name, age) { this.name = name; this.age = age; } let person1 = new Person('Bob', 25); -
工厂函数:使用工厂函数创建对象,这种方式可以隐藏创建对象的细节。
// 定义一个工厂函数来创建人的对象 function createPerson(name, age, job) { // 创建一个新的空对象 const person = {}; // 添加属性和方法到对象中 person.name = name; person.age = age; person.job = job; person.greet = function() { console.log('Hello, my name is ' + this.name); }; // 返回新创建的对象 return person; } // 使用工厂函数创建对象实例 const person1 = createPerson('Alice', 30, 'Engineer'); const person2 = createPerson('Bob', 25, 'Designer'); // 调用对象的方法 person1.greet(); // 输出 "Hello, my name is Alice" person2.greet(); // 输出 "Hello, my name is Bob" ----------------------------------------------------------------------- function createPerson(name, age) { return { name: name, age: age }; } let person2 = createPerson('Charlie', 28); -
原型链:通过原型链创建对象,利用原型链实现对象之间的继承关系。
function Animal() {} Animal.prototype.name = 'Buddy'; let dog = new Animal(); -
ES6类:使用ES6引入的类来创建对象。
class Car { constructor(brand) { this.brand = brand; } } let myCar = new Car('Toyota');
这些是JavaScript中常用的创建对象的几种方式,每种方式都有其适用的场景和特点。在实际开发中,可以根据具体情况选择合适的方式来创建对象。
6.js实现继承列举都有哪些
-
原型链继承: 使用原型链实现继承是JavaScript中最基本的一种继承方式。通过让子类的原型对象指向父类的实例,从而实现属性和方法的继承。
function Parent() { this.parentProperty = 'parent'; } function Child() { this.childProperty = 'child'; } Child.prototype = new Parent(); -------------------------------------------------------------------------------------- function Parent(name) { this.name = name; } Parent.prototype.getName = function() { return this.name; }; function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); const childObj = new Child('John', 25); console.log(childObj.getName()); // 输出: John -
构造函数继承: 利用call()或apply()方法,在子类中调用父类的构造函数,从而实现属性的继承。
function Parent(name) { this.name = name; } Parent.prototype.getName = function() { return this.name; }; function Child(name, age) { Parent.call(this, name); this.age = age; } const childObj = new Child('John', 25); console.log(childObj.getName()); // 输出: John ------------------------------------------------------------------ function Parent() { this.parentProperty = 'parent'; } function Child() { Parent.call(this); this.childProperty = 'child'; } -
组合继承: 结合原型链继承和构造函数继承,既继承了原型链上的方法,又能够传递参数给父类构造函数。
function Parent(name) { this.name = name; } Parent.prototype.getName = function() { return this.name; }; function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; const childObj = new Child('John', 25); console.log(childObj.getName()); // 输出: John -------------------------------------------------------------------------- function Parent() { this.parentProperty = 'parent'; } function Child() { Parent.call(this); this.childProperty = 'child'; } Child.prototype = new Parent(); -
原型式继承: 通过创建一个临时构造函数,将传入的对象作为该构造函数的原型。
承。
function createObject(obj) { function F() {} F.prototype = obj; return new F(); } const parentObj = { name: 'Parent' }; const childObj = createObject(parentObj); console.log(childObj.name); // 输出: Parent -
寄生式继承: 在原型式继承的基础上,对继承的对象进行增强后返回。
function createAnother(original) { var clone = Object.create(original); clone.sayHi = function() { console.log('hi'); }; return clone; } -
ES6中的class继承: ES6引入了class和extends关键字,使得在JavaScript中实现类和继承更加直观和易用。
class Parent { constructor() { this.parentProperty = 'parent'; } } class Child extends Parent { constructor() { super(); this.childProperty = 'child'; } }
区别
-
原型链继承:
- 通过将子类的原型对象指向父类的实例来实现继承。
- 子类实例会继承父类的属性和方法,但也会共享父类实例的属性,可能导致引用类型数据的共享和传递问题。
-
构造函数继承:
- 在子类构造函数中调用父类构造函数,从而实现属性的继承。
- 解决了原型链继承中引用类型数据共享的问题,但每个实例都会单独拥有一份方法的副本。
-
组合继承:
- 结合原型链继承和构造函数继承,即通过调用父类构造函数继承属性,又通过将子类的原型对象指向父类的实例来继承方法。
- 解决了原型链继承和构造函数继承各自的缺点,但在创建实例时会调用两次父类构造函数。
-
原型式继承:
- 利用一个空函数作为中介,将某个对象作为基础创建新对象,从而实现继承。
- 可以通过该方式来复制对象,但没有解决引用类型数据共享的问题。
-
寄生式继承:
- 在原型式继承的基础上,对继承的对象进行增强后返回。
- 可以在不修改对象的情况下为其添加方法,但同样没有解决引用类型数据共享的问题。
-
ES6 中的 class 继承:
- 通过 class 和 extends 关键字更加直观和易用地实现继承。
- 使用 super() 方法调用父类构造函数,解决了组合继承中调用两次父类构造函数的问题。
通过了解这些继承方式的区别,我们可以根据具体的情况选择最适合的继承方式,从而更好地组织和复用代码。
7.原型,原型链
1. 原型
- 在 JavaScript 中,每个对象都有一个原型(prototype),原型本质上就是一个普通的对象。
- 当我们创建一个对象时,这个对象会自动含有一个指向其原型的内部链接。
2. 原型链
- 在 JavaScript 中,对象之间通过原型链进行关联。
- 当访问一个对象的属性或方法时,如果该对象本身没有定义这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到对应的属性或方法,或者查找到原型链的顶端(即 Object.prototype)。
- 这样的属性和方法继承关系就被称为原型链。
3. 实例与原型
- 在 JavaScript 中,每个实例对象都有一个指向其构造函数的原型对象的内部链接。
- 通过原型,实例可以访问到构造函数的属性和方法,同时也可以访问到构造函数原型对象的属性和方法,以及原型对象的原型,形成了一个链式结构。
4. 原型的作用
- 原型允许 JavaScript 对象在创建时共享属性和方法。
- 通过原型,可以实现对象之间的属性和方法的继承关系,从而提供了一种有效的代码复用机制。
5. Object.prototype
- 在 JavaScript 中,所有对象的原型链的顶端是 Object.prototype 对象,它包含了 JavaScript 中所有对象共享的基本方法,比如 toString()、valueOf() 等。
总的来说,原型和原型链是 JavaScript 中非常重要的概念,它们为 JavaScript 对象提供了灵活的继承机制和共享属性的能力。通过原型链,JavaScript 实现了一种基于原型的面向对象编程模型,使得代码的组织和复用更加灵活和高效。
1. 显示原型
-
每个函数(包括构造函数)都有一个名为 "prototype" 的属性,这个属性指向一个对象。
-
这个 "prototype" 对象包含了由特定类型的所有实例共享的属性和方法。
function Person(name) { this.name = name; }
console.log(Person.prototype); // 输出该构造函数的显示原型对象
2. 隐式原型
-
在 JavaScript 中,每个实例对象都含有一个指向其构造函数的原型对象的内部链接,这个链接被称为隐式原型。
-
当我们访问一个实例对象的属性或方法时,JavaScript 引擎会先在实例对象本身查找,如果找不到,就会沿着实例对象的隐式原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶端(Object.prototype)。
function Person(name) { this.name = name; }
const person1 = new Person('Alice'); console.log(person1.proto); // 输出该实例对象的隐式原型对象
3. 关系
- 显示原型是函数的一个属性,隐式原型是实例对象的一个内部链接。
- 函数的显示原型指向的对象会成为实例对象的隐式原型。这样就形成了从实例对象到构造函数的原型对象再到 Object.prototype 的原型链。
通过显示原型和隐式原型的关系,JavaScript 实现了基于原型的继承机制。这种继承方式相对于经典的基于类的继承方式更加灵活,也更符合 JavaScript 动态特性的设计理念。
8.map和forEach的区别
map 和 forEach 都是 JavaScript 中用于遍历数组的方法,它们有一些区别,让我为你详细解释一下:
1. 返回值
forEach:forEach方法会对数组的每个元素执行提供的回调函数,但它不会返回任何值,即使你在回调函数中进行了操作,也不会对原数组产生影响。map:map方法会对数组的每个元素执行提供的回调函数,并将每次回调函数的结果组合起来,形成一个新的数组作为返回值。
2. 对原数组的影响
forEach:forEach方法对原数组没有任何影响,它只是用来遍历数组并对数组元素进行操作。map:map方法不仅可以遍历数组并对元素进行操作,而且还可以生成一个新的数组,新数组的每个元素都是对原数组元素的操作结果。
3. 使用场景
forEach:适合在遍历数组时进行一些操作,比如打印数组元素、修改数组元素等。map:适合在遍历数组时生成一个新的数组,通常用于对原数组进行一些变换操作,比如对每个元素进行加工后生成一个新的数组。
4. 示例代码
-
forEach示例:const arr = [1, 2, 3]; arr.forEach(element => { console.log(element); });
-
map示例:const arr = [1, 2, 3]; const newArr = arr.map(element => element * 2); console.log(newArr); // 输出: [2, 4, 6]
总的来说,forEach 主要用于对数组进行遍历操作,而 map 则主要用于生成一个新的数组,根据原数组经过某种操作得到的结果。选择使用哪个方法取决于你的需求,如果只需要遍历数组并对其进行操作,可以选择 forEach;如果需要生成一个新的数组,可以选择 map。
9.在CSS中,实现水平垂直居中的方法有多种,以下是几种常用的方法:
-
使用Flexbox布局:Flexbox布局是一种方便实现水平垂直居中的方法。
.container { display: flex; justify-content: center; align-items: center; } -
使用绝对定位和transform:利用绝对定位和transform属性可以实现元素的水平垂直居中。
.container { position: relative; } .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } -
使用表格布局:通过display: table和display: table-cell属性实现水平垂直居中。
.container { display: table; width: 100%; height: 100%; } .centered { display: table-cell; vertical-align: middle; text-align: center; } -
使用Grid布局:Grid布局也可以轻松实现水平垂直居中。
.container { display: grid; place-items: center; } -
使用伪元素:利用绝对定位和伪元素来实现水平垂直居中。
.container { position: relative; } .centered::before { content: ''; display: inline-block; height: 100%; vertical-align: middle; } .content { display: inline-block; vertical-align: middle; }
这些是常见的CSS实现水平垂直居中的方法,每种方法都有其适用的场景和特点。在实际开发中,可以根据具体需求选择合适的方法来实现水平垂直居中效果。
10.JavaScript 中常用的设计模式
1. 工厂模式 (Factory Pattern):
-
工厂模式通过提供一个创建对象的接口来创建对象的实例。
function Car(make, model) { this.make = make; this.model = model; }
function CarFactory() { this.createCar = function(make, model) { return new Car(make, model); }; }
// 使用工厂创建对象 const carFactory = new CarFactory(); const myCar = carFactory.createCar('Toyota', 'Camry');
2. 单例模式 (Singleton Pattern):
-
单例模式确保一个类只有一个实例,并提供一个全局访问点。
const Singleton = (function() { let instance;
function createInstance() { // 实例化操作 return { message: "I am a singleton instance" }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } };})();
// 获取单例实例 const singletonInstance = Singleton.getInstance();
3. 观察者模式 (Observer Pattern):
-
观察者模式定义了一种一对多的依赖关系,当一个对象的状态改变时,所有依赖于它的对象都会得到通知。
class Subject { constructor() { this.observers = []; }
addObserver(observer) { this.observers.push(observer); } notifyObservers(message) { this.observers.forEach(observer => observer.update(message)); }}
class Observer { update(message) { console.log(
Received message: ${message}); } }// 使用观察者模式 const subject = new Subject(); const observerA = new Observer(); const observerB = new Observer();
subject.addObserver(observerA); subject.addObserver(observerB);
subject.notifyObservers("Hello observers!");
4. 模块模式 (Module Pattern):
-
模块模式使用闭包封装私有变量和方法,同时返回一个公共接口。
const Module = (function() { let privateVar = 0;
function privateFunction() { return privateVar; } return { increment: function() { privateVar++; }, getValue: function() { return privateFunction(); } };})();
// 使用模块模式 Module.increment(); console.log(Module.getValue()); // 输出: 1
5. 策略模式 (Strategy Pattern):
-
策略模式定义一系列算法,将它们封装起来,并使它们可以相互替换。
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
// 使用策略模式 const strategyAdd = add; const result = strategyAdd(5, 3); // 结果为 8
6. 原型模式 (Prototype Pattern):
-
原型模式使用原型对象来创建新的对象,从而减少对象的创建成本。
unction Animal(type) { this.type = type; }
Animal.prototype.sound = function() { console.log(
${this.type} makes a sound.); };// 使用原型模式 const dog = new Animal('Dog'); dog.sound(); // 输出: Dog makes a sound
7. MVC 模式:
-
MVC (Model-View-Controller) 是一种架构模式,用于组织应用程序的代码,将应用程序分为模型、视图和控制器。
javascript复制代码// 模型 (Model) class UserModel { constructor(name) { this.name = name; }
getName() { return this.name; }}
// 视图 (View) class UserView { render(user) { console.log(
User Name: ${user.getName()}); } }// 控制器 (Controller) class UserController { constructor(user, view) { this.user = user; this.view = view; }
updateUserView() { this.view.render(this.user); }}
// 使用 MVC 模式 const user = new UserModel('John'); const view = new UserView(); const controller = new UserController(user, view); controller.updateUserView(); // 输出: User Name: John
这些是一些常见的设计模式和 MVC 模式的示例代码,它们有助于组织和管理 JavaScript 代码以满足不同的需求和架构。
1. 工厂模式(Factory Pattern)
- 区别:工厂模式主要用来创建对象实例,隐藏了创建对象的复杂性。通过工厂模式,可以根据不同的条件返回不同类的实例。
- 适用场景:当需要根据一些条件创建不同类的实例,并且希望隐藏具体的实例化过程时,可以使用工厂模式。
2. 单例模式(Singleton Pattern)
- 区别:单例模式确保一个类只有一个实例,并提供一个全局访问点。它可以防止多个对象对同一资源的同时访问。
- 适用场景:当需要确保系统中只有一个实例,并且所有的客户都需要共享一个公共实例时,可以使用单例模式。
3. 观察者模式(Observer Pattern)
- 区别:观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
- 适用场景:当一个对象的改变需要同时影响其他对象,并且不知道有多少对象需要更新时,可以使用观察者模式。
4. 模块模式(Module Pattern)
- 区别:模块模式使用闭包封装私有变量,只暴露公共接口,从而实现信息的隐藏和封装。
- 适用场景:当需要将方法和属性组织在一起,同时又希望隐藏其细节时,可以使用模块模式。
5. 策略模式(Strategy Pattern)
- 区别:策略模式定义了一系列算法,并使它们可以相互替换。它让算法独立于使用它的客户而变化。
- 适用场景:当需要在运行时根据不同的情况选择不同的算法时,可以使用策略模式。
6. 原型模式(Prototype Pattern)
- 区别:原型模式通过克隆现有的对象来创建新的对象实例,而不是通过构造函数来创建。
- 适用场景:当需要创建大量相似对象实例,并且这些对象之间部分已有相同的属性时,可以使用原型模式。
7.MVC 模式
- 区别:MVC 是一种架构模式,将应用程序分为三个核心部分:模型(Model)、视图(View)和控制器(Controller)。模型负责管理应用程序的数据和业务逻辑,视图负责显示数据给用户,控制器负责处理用户交互并更新模型和视图。
- 适用场景:MVC 模式适用于需要清晰分离数据、表示和处理的应用程序。它提供了一种结构化的方法来组织代码,使得应用程序更易于维护和扩展。
在 JavaScript 中,MVC 模式通常被用于构建单页应用(SPA)或前端框架中,比如 Angular、React、Vue 等。控制器接收用户的输入并相应地更新模型和视图,模型包含应用程序的数据和业务逻辑,视图负责展示数据给用户并接收用户的输入。这种分离使得代码更易于理解和维护,同时也有利于团队合作开发。
总而言之,MVC 模式是一种非常有用的架构模式,适用于需要清晰分离数据、表示和处理的应用程序。通过合理地运用 MVC 模式,可以使得 JavaScript 应用程序更易于开发、维护和扩展
11. Javascript 中的类型转换机制
javaScript 中的类型转换机制是非常灵活的,它允许你在不同类型之间进行自动或显式的转换。这种灵活性使得 JavaScript 在处理不同类型数据时非常方便,但也可能导致一些意外行为。
-
隐式类型转换:在某些情况下,JavaScript 会自动对值进行类型转换,比如在进行不同类型之间的操作时。比如:
javascriptCopy Code1 + '2'; // 结果为 '12',数字 1 被转换为字符串与 '2' 进行拼接 -
显式类型转换:你也可以通过一些内置函数来显式地进行类型转换,比如
parseInt()、parseFloat()、String()、Number()、Boolean()等:javascriptCopy Codevar numString = '123'; var num = Number(numString); // 将字符串 '123' 转换为数字 123 -
强制转换规则:在条件判断中,JavaScript 会根据一定的规则将值转换为布尔类型。比如在 if 语句中,以下值会被转换为 false:
false0和-0''(空字符串)null和undefinedNaN(不是一个数字)
-
对象转换:对象在类型转换时会根据上下文进行转换,通常会调用对象的
toString()或valueOf()方法来获取基本值。
总体来说,JavaScript 的类型转换机制非常灵活,但同时也需要小心使用,特别是在条件判断和类型转换的场景中,以避免意外的行为。
12.web常见的攻击方式有
常见的Web攻击方式包括跨站脚本攻击(XSS)、跨站请求伪造(CSRF)、SQL注入、点击劫持等。以下是开发过程中可以采取的一些防御措施:
-
跨站脚本攻击(XSS):
- 输入验证和过滤:对用户输入进行严格的验证和过滤,避免将未经处理的用户输入直接插入到页面中。
- 输出转义:在将用户输入展示到页面上时,对特殊字符进行转义,例如将"<"转义为"<"。
-
跨站请求伪造(CSRF):
-
使用CSRF Token:为每个用户会话生成一个唯一的令牌,并将该令牌随每个请求一起发送,以验证请求的来源。
-
双重提交cookie验证:在执行敏感操作时,要求客户端同时发送一个cookie和一个请求头,两者应该匹配。
-
Token 验证
token不是为了防止XSS的,而是为了防止CSRF的。CSRF攻击的原因是浏览器会自动带上cookie,而不会带上token。
- 服务器发送给客户端一个token。
- 客户端提交的表单中带着这个token,也可以把 token 隐藏在 http 的 header头中。
- 如果这个 token 不合法,那么服务器拒绝这个请求。
-
-
SQL注入:
- 使用参数化查询或预编译语句:避免通过字符串拼接构建SQL查询,而是使用参数化的SQL查询或者预编译语句。
- 限制数据库账户权限:数据库账户应该被赋予最小必要的权限,避免具有执行DDL语句的权限。
-
点击劫持:
- 使用Frame Busting技术:向网页中添加frame busting代码,防止页面被嵌入到iframe中。
- X-Frame-Options头部:设置HTTP响应头部中的X-Frame-Options,来控制页面的嵌入行为。
CSRF和XSS的区别
- CSRF需要登陆后操作,XSS不需要
- CSRF是请求页面api来实现非法操作,XSS是向当前页面植入js脚本来修改页面内容。
除了上述措施外,还有其他一些常见的安全措施,如使用HTTPS协议传输数据以保证通信安全、实施内容安全策略(CSP)来限制资源加载、使用安全的加密算法来存储用户密码等。
综合来看,开发人员在进行Web开发时应当始终牢记安全问题,并在设计和编码过程中采取相应的措施来防范各种类型的攻击。同时,定期进行安全审计和漏洞扫描也是保护Web应用安全的重要手段。
AXIOS XHR 和 Fetch 的区别
你知道 XHR 和 Fetch 的区别吗? - 掘金 (juejin.cn)
(1)AJAX
Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:
- 本身是针对MVC编程,不符合前端MVVM的浪潮
- 基于原生XHR开发,XHR本身的架构不清晰
- 不符合关注分离(Separation of Concerns)的原则
- 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
(2)Fetch
fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
fetch的优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
fetch的缺点:
- fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
- fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
- fetch没有办法原生监测请求的进度,而XHR可以
(3)Axios
Axios 是一种基于Promise封装的HTTP客户端,其特点如下:
-
浏览器端发起XMLHttpRequests请求
-
node端发起http请求
-
支持Promise API
-
监听请求和返回
-
对请求和返回进行转化
-
取消请求
-
自动转换json数据
-
客户端支持抵御XSRF攻击
基于webpack进行前端优化
-
代码压缩:使用webpack的UglifyJsPlugin或TerserPlugin插件对JavaScript代码进行压缩,以减小文件体积,提高加载速度。
const TerserPlugin = require('terser-webpack-plugin');
module.exports = { //... optimization: { minimize: true, minimizer: [new TerserPlugin()], }, };
-
代码分割:使用Webpack的动态import语法或者SplitChunksPlugin插件将应用拆分为多个小模块,按需加载,减小初始加载体积。
module.exports = { optimization: { splitChunks: { chunks: 'all', }, }, };
-
文件合并:使用webpack的SplitChunksPlugin插件将公共模块抽离出来,减少重复加载,提高缓存命中率。
module.exports = { //... optimization: { splitChunks: { chunks: 'all', }, }, };
-
图片优化:使用url-loader或者file-loader加载图片,并结合image-webpack-loader对图片进行压缩和优化处理。
module.exports = { module: { rules: [ { test: /.(png|svg|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, // 小于8KB的图片会被转成base64格式 }, }, 'image-webpack-loader', // 图片压缩 ], }, ], }, };
-
持久化缓存:使用webpack的HashedModuleIdsPlugin插件为模块生成独立的哈希值,保证每次修改代码后只有相应的文件名发生变化,避免浏览器缓存失效。
const { HashedModuleIdsPlugin } = require('webpack');
module.exports = { plugins: [ new HashedModuleIdsPlugin(), ], };
-
懒加载和预加载:使用webpack的动态import语法实现懒加载,以及使用preload和prefetch优化页面资源加载顺序和时机。
javascriptCopy Code// 懒加载 import('./moduleA').then(moduleA => { // 使用moduleA });
// 预加载 import(/* webpackPrefetch: true */ './moduleB').then(moduleB => { // 预加载moduleB });
以上是一些基于webpack的前端优化技术和配置,通过合理地利用webpack的各种功能和插件,可以有效地提升前端应用的性能和用户体验。
webpack
做了几年前端,别跟我说没配置过webpack - 掘金 (juejin.cn)
const path = require('path');
module.exports = {
// ...其他配置
entry: {
app1: ['./src/app1.js', './src/aaa.js', './src/dddd.js']
},
output: {
filename: 'app1.bundle.js',
path: path.resolve(__dirname, 'dist/app1')
}
};
module.exports = {
// ...其他配置
entry: {
app1: './src/app1.js',
app2: './src/app2.js',
app3: './src/app3.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
在上面的示例中,我们定义了三个入口点:app1.js、app2.js和app3.js。它们分别被命名为 app1、app2和app3。Webpack 会根据这些入口点逐个解析它们的依赖关系并打包到相应的输出文件中。
当你运行 Webpack 进行打包时,每个入口点都会生成一个对应的输出文件。例如,app1.js会生成 app1.bundle.js,app2.js会生成 app2.bundle.js,app3.js会生成 app3.bundle.js。
Polyfill(填充物)补丁
Polyfill 是一种用于填充浏览器或环境中缺失功能的代码。它可以让你在较旧的浏览器或不支持新功能的环境中使用新的 JavaScript API 或语言特性。
常见的 Polyfill 库包括 **core-js**、**babel-polyfill** 等;
babel 和 babel ployfill 的关系
1、先来理解下 babel 到底是做什么的?
简单来讲,babel解决语法层面的问题。用于将ES6+的高级语法转为ES5。
2、babel polyfill 又是做什么的?
如果要解决API层面的问题,需要使用垫片。比如常见的有babel-polyfill、babel-runtime 和 babel-plugin-transform-runtime。
理清了他们之间的关系,那么再正式来讲讲有关polyfill的二三事。
polyfill 种类
babel polyfill 有三种
markdown复制代码* babel-polyfill
* babel-runtime
* babel-plugin-transform-runtime
babel-polyfill
**babel-polyfill**通过向全局对象和内置对象的prototype上添加方法来实现的。所以这会造成全局空间污染。
babel-polyfill使用的两种方式
1、webpack.config.js 中: 配置webpack.config.js里的entry设置为entry: ['babel-polyfill',path.join(__dirname, 'index.js')]
2、业务 js 中: 在webpack.config.js配置的主入口index.js文件的最顶层键入
import 'babel-polyfill'
两者打印出来的大小都是一样的,打包后大小是280KB,如果没有使用babel-polyfill,大小是3.43kb。两则相差大概81.6倍。原因是webpack把babel-polyfill整体全部都打包进去了。而babel-polyfill肯定也实现了所有ES6新API的垫片,文件一定不会小。
那么有没有一种办法,根据实际代码中用到的ES6新增API ,来使用对应的垫片,而不是全部加载进去呢? 是的,有的。那就是 babel-runtime & babel-plugin-transform-runtime,他们可以实现按需加载。
babel-runtime
简单说 babel-runtime 更像是一种按需加载的实现,比如你哪里需要使用 Promise,只要在这个文件头部
import Promise from 'babel-runtime/core-js/promise'
就行了。
不过如果你许多文件都要使用 Promise,难道每个文件都要 import 一下吗?当然不是,Babel 官方已考虑这种情况,只需要使用 babel-plugin-transform-runtime 就可以解决手动 import 的苦恼了。
babel-plugin-transform-runtime
babel-plugin-transform-runtime 装了就不需要装 babel-runtime了,因为前者依赖后者。 总的来说,babel-plugin-transform-runtime 就是可以在我们使用新 API 时 自动 import babel-runtime 里面的 polyfill,具体插件做了以下三件事情:
- 当我们使用 async/await 时,自动引入 babel-runtime/regenerator
- 当我们使用 ES6 的静态事件或内置对象时,自动引入 babel-runtime/core-js
- 移除内联 babel helpers 并替换使用 babel-runtime/helpers 来替换
babel-plugin-transform-runtime 优点:
- 不会污染全局变量
- 多次使用只会打包一次
- 依赖统一按需引入,无重复引入,无多余引入
- 避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积
使用方式
在 .babelrc 中配置:
plugins: ["tranform-runtime"]
打包后大小为 17.4kb,比之前的280kb要小很多。
Plugin 插件
1、官方 Presets
如果不想自己设置一堆插件的话,官方有env,react,flow三个 Presets。即预安装了 plugins 的配置。
presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性, presets 是一组 Plugins 的集合。如:
babel-preset-es2015: 可以将es6的代码编译成es5
babel-preset-es2016: 可以将es7的代码编译为es6
babel-preset-es2017: 可以将es8的代码编译为es7
babel-preset-latest: 支持现有所有ECMAScript版本的新特性
当我们需要转换es6语法时,可以在 .babelrc 的 plugins 中按需引入一下插件,比如:check-es2015-constants、es2015-arrow-functions、es2015-block-scoped-functions等等几十个不同作用的 plugin。.babelrc 中配置项可能是如下方式:
json复制代码{
"plugins": [
"check-es2015-constants",
"es2015-arrow-functions",
"es2015-block-scoped-functions",
// ...
]
}
但 Babel 团队为了方便,将同属 ES2015 的几十个 Transform Plugins 集合到 babel-preset-es2015 一个 Preset 中,这样我们只需要在 .babelrc 的 presets 加入 ES2015 一个配置就可以完成全部 ES2015 语法的支持了。.babelrc 中配置如下:
json复制代码{
"presets": [
"es2015"
]
}
2、Stage-X(试验性 Presets)
这个比较好理解,就是为了支持 TC39 对于草案阶段的 ES 最新特性而开发的 presets。
- Stage 0 - 草稿:只是一个设想可能是一个 Babel 插件
- Stage 1 - 提案:值得去推进的东西
- Stage 2 - 草案:初始化规范
- Stage 3 - 候选:完整规范和初始化浏览器实现
- Stage 4 - 完成:会加入到下一版中
3、转换插件
官方和民间提供了很多的转换插件,用于指定对某一项 ES 高级特性进行解析(parse)和转换,比如箭头函数。 官方见:babeljs.io/docs/en/plu…
它与babel polyfill区别是:
4、语法插件
这种插件可以让Babel来解析特殊类型的语法。
json复制代码{
"parserOpts": {
"plugins": ["jsx", "flow"]
}
}
5、插件开发
开发采用了 AST 抽象语法树,类似于 Eslint 插件开发。
csharp复制代码export default function () {
return {
visitor: {
Identifier(path) {
const name = path.node.name;
// reverse the name: JavaScript -> tpircSavaJ
path.node.name = name.split("").reverse().join("");
}
}
};
}
webpack 中使用 babel 须知
babel 用途是语法转换,所以webpack 中需要用到 babel-loader。而 babel-core 是 Babel 编译器的核心,因此也就意味着如果我们需要使用 babel-loader 进行 es6 转码的话,我们首先需要安装 babel-core。
总结
使用场景建议:
- 开发应用建议使用 babel-polyfill 会在全局新增对应方法
- 开发框架建议 babel-plugin-transform-runtime 局部变量 不会污染全局,局部使用es6的函数或方法
如何把 Vite 的打包时间从 110s 优化到 25s 的
我是如何把 Vite 的打包时间从 110s 优化到 25s 的 - 掘金 (juejin.cn)
1.安装 rollup-plugin-visualizer 插件,此插件可以展示构建时长、chunk 数量及大小,是分析构建的绝佳利器。
2.配置好分析插件后,执行 构建命令,此时在根目录下会生成一个 stats.html 文件,此文件就是当前项目打包的chunk 分析图。
优化方案
1.路由动态引入
方案
路由全部采用动态引入的方式加载,减小首页 chunk 生成时间,打包的chunk越大耗时也就越多,从大chunk中分离出小chunk可以有效减少打包时间,同时提高首页的加载速度。
2.配置 CDN 加速(公司内部的oss)
3.优化 polyfill 导入
避免全量引入bable-polyfill,按需加载
作者:程序员小杨v1
链接:juejin.cn/post/723581…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4.
- 使用
vite-plugin-compress插件对文件进行 gzip 压缩,减小构建包体积 - 使用
vite-plugin-imagemin插件压缩图片,减小构建包体积
箭头函数和普通函数的区别
-
箭头函数没有自己的
this,它的this始终指向定义时所在的对象。 -
箭头函数不能通过
new关键字调用,也不能作为构造函数使用。 -
箭头函数没有
arguments对象,需要使用剩余参数(...args)来获取参数列表。 -
箭头函数不能用作生成器函数。
js的装饰器
JavaScript 的装饰器是一种在类、方法或属性前面以 @ 符号使用的语法,用于修改或扩展它们的行为。装饰器通常用于给类、方法或属性添加额外的功能,而不需要修改它们的原始定义。
在 JavaScript 中,装饰器通常与一些流行的框架(如React、Vue、MobX等)或者一些实验性的提案(如 ECMAScript 装饰器提案)一起使用。
下面是一些常见的装饰器的使用场景:
-
类装饰器:
- 类装饰器可以用来修改类的行为,例如添加静态属性、实例属性或者修改类的行为逻辑。
- 在框架中,类装饰器常用于声明组件、路由等元信息,或者在类被实例化时执行一些初始化逻辑。
-
function logClassName(target: any) { console.log(`Class name: ${target.name}`); } @logClassName class MyClass { // 类的定义 } // Output: Class name: MyClass
-
方法装饰器:
- 方法装饰器可以用来修改类的方法,例如添加日志、权限验证、性能监控等功能。
- 在一些框架或库中,方法装饰器常用于声明路由处理函数、事件处理函数等。
-
function logMethod(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Method ${key} called with arguments: ${JSON.stringify(args)}`); return originalMethod.apply(this, args); }; return descriptor; } class MyClass { @logMethod greet(name: string) { console.log(`Hello, ${name}!`); } } const obj = new MyClass(); obj.greet("John"); // Output: // Method greet called with arguments: ["John"] // Hello, John!
-
属性装饰器:
- 属性装饰器可以用来修改类的属性,例如添加数据校验、数据转换、缓存等功能。
- 在一些框架或库中,属性装饰器常用于声明组件的属性或状态管理的相关信息。
-
function uppercase(target: any, key: string) { let value = target[key]; const getter = function () { return value; }; const setter = function (newValue: string) { value = newValue.toUpperCase(); }; Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true, }); } class MyClass { @uppercase name: string = "John"; } const obj = new MyClass(); console.log(obj.name); // Output: JOHN obj.name = "Alice"; console.log(obj.name); // Output: ALICE
在原生 JavaScript 中并不是所有环境和工具都能直接支持装饰器语法。
为什么JavaScript函数没有装饰器
对于为什么 JavaScript 函数没有装饰器这个问题,实际上装饰器最初是作为 ECMAScript 的一个提案出现的,旨在为类和类的成员(包括方法和属性)提供一种声明式的扩展机制。由于函数本身并不是类的成员,它是 JavaScript 中的一级对象,所以在最初的装饰器提案中并没有包括对函数的装饰器支持。
另外,JavaScript 中函数的灵活性已经相当高了,可以通过原型链、高阶函数、闭包等多种方式来扩展和修改函数的行为,因此可能并没有像类那样急需装饰器这样的声明式扩展机制。当然,这只是一个推测,并不代表函数装饰器就没有任何用武之地。
需要注意的是,虽然原生 JavaScript 目前并不支持函数装饰器,但是在某些框架和工具(如 Babel、TypeScript)中可能会有对函数装饰器的支持,或者用户可以通过其他方式来实现对函数的装饰和扩展。
事件循环任务顺序
事件循环(Event Loop)是JavaScript中用于管理异步代码执行的机制,它负责处理调度执行任务队列中的任务。在浏览器或Node.js环境中,事件循环的任务执行顺序通常包括以下几个阶段:
-
执行同步任务: 首先执行当前执行栈中的同步任务,直到执行栈为空。
-
执行微任务(microtask): 当执行栈中的同步任务执行完毕后,会立即执行所有微任务队列中的微任务。常见的微任务包括Promise回调和MutationObserver。
-
更新渲染: 如果是浏览器环境,在执行完微任务后,浏览器会进行页面渲染。
-
执行宏任务(macrotask): 在执行完微任务并完成页面渲染后,开始执行宿主环境提供的宏任务,包括setTimeout/setInterval回调、I/O操作和事件回调等。
-
回到第一步: 重复以上步骤,不断循环执行。
这个事件循环的过程保证了 JavaScript 中的异步任务能够按照一定的顺序被执行,同时也保证了即使执行栈中有大量的任务,页面依然可以及时地进行渲染响应用户操作。
需要注意的是,微任务和宏任务的执行顺序是固定的,即在每一次循环中,先执行完所有的微任务,然后再执行宏任务。这个特性保证了微任务的优先级高于宏任务,可以在下一个宏任务执行之前及时处理一些重要的异步任务。
总之,事件循环通过上述步骤来管理和执行 JavaScript 中的异步任务,保证了任务执行的顺序和及时性。
for in 和for of的区别
for...in 和 for...of 是 JavaScript 中用于遍历数据结构(如数组、对象、字符串等)的两种不同的循环语句。它们之间有以下区别:
-
适用范围:
for...in循环用于遍历对象的可枚举属性。for...of循环用于遍历可迭代对象(如数组、字符串、Set、Map等),并且会遍历对象的可迭代属性值。
-
返回值:
- 在
for...in循环中,迭代的是对象的键名(或者数组的索引)。 - 在
for...of循环中,迭代的是对象的键值(或者数组的元素值)。
- 在
-
遍历顺序:
for...in循环遍历对象的属性时,并不保证属性的顺序,因为对象属性的顺序在规范中并不保证。for...of循环遍历可迭代对象时,会按照对象的迭代器规则来依次访问对象的每个属性值。
下面是使用这两种循环的示例:
// for...in 遍历对象的属性
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key); // 输出:a, b, c
console.log(obj[key]); // 输出:1, 2, 3
}
// for...of 遍历数组的元素
const arr = [1, 2, 3];
for (let value of arr) {
console.log(value); // 输出:1, 2, 3
}
// for...of 遍历字符串
const str = "hello";
for (let char of str) {
console.log(char); // 输出:h, e, l, l, o
}
for in 会遍历对象的所有属性吗
在 JavaScript 中,for...in 循环会遍历对象自身的可枚举属性以及继承的可枚举属性。这意味着除了遍历对象自身定义的属性外,还会遍历它从原型链继承的属性。
如果你只想遍历对象自身的属性而不包括继承的属性,可以通过结合 **hasOwnProperty** 方法来实现。例如:
const obj = { a: 1, b: 2 };
Object.prototype.c = 3; // 在原型上添加属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key); // 输出:a, b
console.log(obj[key]); // 输出:1, 2
}
}