undefined
:未定义的值null
:表示空值或不存在的对象boolean
:布尔值,即 true
或 false
number
:数字,包括整数和浮点数string
:字符串,用单引号或双引号括起来的文本object
:普通对象,包括对象字面量、数组、函数等symbol
:唯一且不可改变的数据类型,通常作为对象属性的标识符function
:表示可执行的代码块,可以被调用使用typeof
运算符: 可以使用typeof
运算符来确定一个值的数据类型。它返回一个表示数据类型的字符串。例如:
console.log(typeof 42); // 输出 "number"
console.log(typeof "Hello"); // 输出 "string"
console.log(typeof true); // 输出 "boolean"
使用instanceof
运算符: instanceof
运算符可以用来检查对象是否属于某个特定的构造函数。例如:
const arr = [1, 2, 3];
console.log(arr instanceof Array); // 输出 "true"
使用Array.isArray()
方法: Array.isArray()
方法用于检查一个值是否为数组类型。例如:
console.log(Array.isArray([1, 2, 3])); // 输出 "true"
使用Object.prototype.toString
方法: 可以通过调用Object.prototype.toString
方法并将要检查的值作为参数传入,来获取其精确的数据类型信息。例如:
console.log(Object.prototype.toString.call(42)); // 输出 "[object Number]"
console.log(Object.prototype.toString.call("Hello")); // 输出 "[object String]"
转换为字符串(String)类型:
使用toString()和String()方法:toString不可以转undefined,null
let num = 42;
let str1 = String(num);
let str2 = num.toString();
转换为数字(Number)类型:
使用Number()和parseInt()等方法:
let str = "42";
let num1 = Number(str);
let num2 = parseInt(str); // 解析整数
let num3 = parseFloat("3.14"); // 解析浮点数
转换为布尔(Boolean)类型:
使用Boolean()函数:
let a = 42;
let b = ""; // 空字符串
let bool1 = Boolean(a); // true
let bool2 = Boolean(b); // false
转换为数组(Array)类型:
使用Array.from()方法将类似数组的对象转换为真正的数组:
let arrayLikeObj = { 0: "a", 1: "b", length: 2 };
let arr = Array.from(arrayLikeObj); // ["a", "b"]
转换为对象(Object)类型:
使用对象字面量或构造函数进行对象的创建:
let obj1 = {}; // 空对象
let obj2 = new Object();
变量提升是 JavaScript 中的一种特性,它是指在代码执行前,JavaScript 引擎会将变量和函数的声明提升到其所在作用域的顶部。这意味着可以在声明之前访问这些变量或函数。
在 JavaScript 中,变量提升主要包括两个方面:
变量声明的提升: 变量声明使用 var
、let
或 const
关键字进行,而变量的赋值操作则不会被提升。如果在代码中先使用了一个变量,然后才进行声明,JavaScript 会将该声明提升到作用域的顶部。但是变量的初始值仍然是 undefined
。例如:
console.log(foo); // 输出 undefined
var foo = "Hello";
函数声明的提升: JavaScript 中的函数可以先使用后声明,因为函数声明也会被提升到作用域的顶部。这意味着你可以在函数声明之前调用函数。例如:
greet(); // 输出 "Hello"
function greet() {
console.log("Hello");
}
需要注意的是,变量提升只会提升声明,而不会提升赋值操作。如果变量没有使用 var
、let
或 const
声明,它将被视为全局对象的属性。此外,使用 let
和 const
声明的变量在块级作用域内存在暂时性死区(TDZ),也会影响到变量的提升行为。
push()
:向数组末尾添加一个或多个元素,并返回新数组的长度。pop()
:删除并返回数组的最后一个元素。unshift()
:向数组开头添加一个或多个元素,并返回新数组的长度。shift()
:删除并返回数组的第一个元素。concat()
:将两个或多个数组合并为一个新数组。slice()
:返回数组的指定部分(浅拷贝)。splice()
:从数组中添加/删除元素,或替换元素。forEach()
:对数组的每个元素执行提供的函数。map()
:创建一个新数组,其结果是对原数组的每个元素应用提供的函数。filter()
:创建一个新数组,其中包含通过提供函数的测试的所有元素。indexOf()
: 返回指定元素在数组中第一次出现的索引,如果不存在则返回-1。例如:includes()
: 判断数组是否包含指定元素,返回布尔值。例如:find()
: 返回数组中满足条件的第一个元素,如果没有找到则返回 undefined。该方法接受一个回调函数作为参数,在回调函数中定义匹配条件。例如:findIndex()
: 返回数组中满足条件的第一个元素的索引,如果没有找到则返回 -1。与 find()
方法类似,也接受一个回调函数作为参数。例如:使用 Set 数据结构:Set 对象存储唯一值的集合,可以将数组转换为 Set,然后再将 Set 转回数组即可去重。例如:
const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
使用 filter() 方法结合 indexOf() 方法:利用数组的 filter() 方法和 indexOf() 方法,对数组进行遍历筛选,只保留首次出现的元素。例如:
const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index);
console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
使用 reduce() 方法:利用数组的 reduce() 方法,遍历数组,将不重复的元素放入新的数组。例如:
创建一个空对象 (或者使用已存在的对象)。
const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = arr.reduce((acc, curr) => {
if (!acc.includes(curr)) {
acc.push(curr);
}
return acc;
}, []);
console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
遍历数组,对于每个元素进行以下操作:
javascript复制代码const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const obj = {};
const uniqueArr = [];
arr.forEach(item => {
if (!obj[item]) {
obj[item] = true;
uniqueArr.push(item);
}
});
console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
在上述代码中,obj
对象用于存储数组元素是否已经出现过。遍历数组时,如果当前元素不在 obj
对象中,则将其添加到 uniqueArr
数组中,并将 obj[item]
设置为 true
标记已经出现过。这样就可以通过对象的属性来实现数组去重。
尾递归(Tail Recursion)是指一个函数在其最后一步调用自身的递归形式。在尾递归中,递归调用是整个函数的最后一条语句,不会有其他的操作或表达式需要在递归调用之后执行。
尾递归有一个重要的特性:在每次递归调用之前,不会再进行任何其他的操作或计算,而是直接将递归调用的结果作为当前调用的结果返回。这样的尾递归称为尾递归优化(Tail Call Optimization)。尾递归优化可以避免由于递归过深导致的堆栈溢出问题,因为每次递归调用都会复用当前函数的栈帧,不会产生额外的堆栈空间消耗。
尾递归和普通递归不同,在普通递归中,每次递归调用都会形成一个新的调用帧,保存当前状态和上下文信息。而尾递归只保留一个调用帧,可以减少内存的使用和函数调用的开销。
尾递归常见的应用是实现阶乘、斐波那契数列等递归算法。通过使用尾递归优化,可以提高性能并避免堆栈溢出。
需要注意的是,并非所有的编程语言和编译器都对尾递归进行优化。一些编程语言和编译器提供了对尾递归的优化支持,而另一些并没有进行优化,仍然可能存在堆栈溢出的问题。在使用尾递归时,需要考虑目标平台和编译器的特性。
数组扁平化是指将多维数组转换为一维数组的操作。在 JavaScript 中,我们可以使用多种方式来实现数组的扁平化。
以下是几种常用的方法:
function flattenArray(arr) {
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
// 如果当前元素是数组,则递归调用flattenArray函数进行扁平化处理
result = result.concat(flattenArray(item));
} else {
// 否则将当前元素添加到结果数组中
result.push(item);
}
});
return result;
}
2.使用Array.flat()
方法(ES2019):
const arr = [1, [2, [3, 4]]];
const flattenedArray = arr.flat(Infinity);
console.log(flattenedArray); // 输出: [1, 2, 3, 4]
3.使用reduce()
方法和递归:
function flattenArray(arr) {
return arr.reduce((result, item) => {
if (Array.isArray(item)) {
// 如果当前元素是数组,则递归调用flattenArray函数进行扁平化处理,并将结果与累加器合并
result = result.concat(flattenArray(item));
} else {
// 否则将当前元素添加到累加器中
result.push(item);
}
return result;
}, []);
}
1 + '2'
: 这个表达式中,1
是一个数字类型,而'2'
是一个字符串类型。当+
运算符的一个操作数是字符串时,它会将另一个操作数也转换为字符串,并执行字符串拼接操作。因此,结果是一个字符串 '12'
。1 - '2'
: 在这个表达式中,1
是一个数字类型,而'2'
也是一个字符串类型。当-
运算符的操作数都是字符串或包含非数字字符时,它会尝试将这些字符串转换为数字并执行减法操作。在这种情况下,'2'
可以被成功转换为数字 2
,然后进行减法运算。因此,结果是一个数字 -1
。综上所述,1 + '2'
的结果是字符串 '12'
,而1 - '2'
的结果是数字 -1
。
trim()
相当于去除字符串中所有的空字符,可以使用 replace(),
正则表达式(RegEx)
this
)调用构造函数。事件委托(event delegation)和事件代理(event delegation)是指同一种概念,在 JavaScript 中常用于处理事件的优化和性能提升。
事件委托/代理基本思想如下:
通过事件委托/代理的方式,可以实现以下几个好处:
事件冒泡:
事件捕获:
addEventListener
)在 JavaScript 中,事件捕获是事件传播的一种阶段,它发生在事件冒泡之前。要开启事件捕获阶段,可以使用 addEventListener
方法的第三个参数,将其设置为 true
。
示例代码如下所示:
var element = document.getElementById('myElement');
element.addEventListener('click', function(event) {
console.log('捕获阶段');
}, true);
在上述示例中,我们通过将 addEventListener
的第三个参数设置为 true
,即开启了捕获阶段。当在 myElement
元素上触发 click 事件时,会先执行在捕获阶段注册的事件处理函数。
需要注意的是,大多数情况下不必显式开启捕获阶段,因为默认情况下事件会在冒泡阶段进行处理。捕获阶段和冒泡阶段共同构成了事件传播的过程。
总结起来:
默认情况下,事件会在冒泡阶段进行处理。如果需要开启捕获阶段,可以将 addEventListener
方法的第三个参数设置为 true
。
在 JavaScript 中,要阻止事件冒泡(即停止事件向父元素的传播),可以使用 event.stopPropagation()
方法。
示例代码如下所示:
var element = document.getElementById('myElement');
element.addEventListener('click', function(event) {
event.stopPropagation(); // 阻止事件冒泡
console.log('点击了子元素');
});
document.addEventListener('click', function(event) {
console.log('点击了文档');
});
在上述示例中,当点击 myElement
元素时,会触发子元素的点击事件处理函数,并输出 “点击了子元素”。由于调用了 event.stopPropagation()
,事件不会继续向上层元素传播,因此不会触发文档上的点击事件处理函数。
需要注意的是,event.stopPropagation()
只能阻止事件在当前元素及其祖先元素之间的传播,无法阻止事件在同级元素之间的传播。如果希望完全取消事件的传播,包括同级元素,可以使用 event.stopImmediatePropagation()
方法。
总结起来:
event.stopPropagation()
用于阻止事件冒泡,只影响当前元素及其祖先元素之间的传播。event.stopImmediatePropagation()
用于完全取消事件的传播,包括同级元素。请注意,以上操作是按照默认的事件冒泡阶段进行处理的。如果在捕获阶段注册了事件处理函数,并且希望阻止捕获阶段中的事件传播,可以在捕获阶段的事件处理函数中使用 event.stopPropagation()
或 event.stopImmediatePropagation()
方法。
ES6(ECMAScript 2015)引入了类(Class)的概念,使得 JavaScript 中面向对象编程更加直观和易用。类是一种特殊的函数,用于创建对象,具有属性和方法。
以下是关于 ES6 类的主要理解:
class
关键字来定义一个类,后跟类名。类名通常采用大写字母开头的驼峰命名法。constructor
关键字来声明。构造函数在使用 new
关键字实例化类时自动调用,用于初始化对象的属性。new
关键字和类名,可以创建类的实例(对象)。实例会继承类的属性和方法,并且每个实例拥有自己的独立数据。extends
关键字,子类可以继承父类的所有成员。子类可以覆盖父类的方法或添加新的方法。super
关键字:super
关键字用于从子类访问父类的属性和方法,可以在子类的构造函数和方法中调用。static
关键字来声明。静态方法属于类本身而不是实例,无需实例化即可调用。使用 ES6 类的优势在于提供了更加面向对象的编程方式,使代码更结构化和易读,同时也方便了继承和代码复用。它更符合传统面向对象编程语言的习惯,让 JavaScript 开发者更容易理解和使用。
静态属性是指属于类本身而不是类的实例的属性。可以通过在类中直接定义变量来创建静态属性。静态属性在所有实例之间共享相同的值,可以在类内部或外部访问。
静态方法是属于类本身的方法,而不是类的实例。可以使用 static
关键字来声明静态方法。静态方法可以在类的实例化过程中使用,并且无法访问实例的属性和方法。它们通常用于执行与类相关但不依赖于实例状态的操作。
下面是一个示例代码来说明静态属性和静态方法的用法:
class MyClass {
// 静态属性
static myStaticProperty = 'This is a static property';
// 静态方法
static myStaticMethod() {
console.log('This is a static method');
}
// 实例方法
myMethod() {
console.log('This is an instance method');
}
}
// 访问静态属性
console.log(MyClass.myStaticProperty); // 输出: "This is a static property"
// 调用静态方法
MyClass.myStaticMethod(); // 输出: "This is a static method"
// 创建类的实例
const myObject = new MyClass();
// 调用实例方法
myObject.myMethod(); // 输出: "This is an instance method"
注意,在调用静态属性和静态方法时,不需要实例化类。可以直接通过类名进行访问。
静态属性和静态方法提供了一种在类级别上处理数据和操作的方式,并且可以在不创建类的实例的情况下使用它们。适用于那些与实例无关但与类相关的功能。
封装一个选项卡的面向对象原理是将选项卡相关的属性和方法封装在一个类中,使得创建和管理选项卡变得更加方便和可复用。以下是使用类封装选项卡的基本原理:
class
关键字定义一个类。class Tab {
// 构造函数
constructor(container) {
this.container = container;
this.tabs = container.querySelectorAll('.tab');
this.tabContents = container.querySelectorAll('.tab-content');
// 其他属性...
}
// 方法...
}
activateTab
方法来激活指定索引的选项卡。activateTab(index) {
// 移除当前激活状态
this.tabs.forEach((tab) => {
tab.classList.remove('active');
});
this.tabContents.forEach((content) => {
content.classList.remove('active');
});
// 激活指定索引的选项卡
this.tabs[index].classList.add('active');
this.tabContents[index].classList.add('active');
}
const tabContainer = document.getElementById('tab-container');
const tabInstance = new Tab(tabContainer);
闭包(Closure)是指函数与其相关的引用环境组合而成的实体。它是由函数以及在该函数被创建时可访问的变量环境组合而成的包裹(或封闭)的范围。简单地说,闭包是一个函数可以访问和操作其外部作用域中的变量,即使在函数被调用之后仍然有效。
闭包的优点:
闭包的缺点:
总体而言,闭包是一项强大的功能,可以在JavaScript中实现许多有用的模式和特性。但在使用闭包时,需要谨慎避免产生不必要的内存开销和潜在的性能问题。
闭包的沙箱模式是指通过闭包创建一个独立的执行环境,用于封装和保护代码,以防止外部代码对其中的变量进行干扰或访问。
在沙箱模式下,使用闭包将函数和相关的变量封装在一个作用域内,形成一个私有的执行环境。这个执行环境类似于一个隔离的沙箱,它与外部环境相互独立,内部的变量和函数不会被外部环境所污染或访问。
沙箱模式的特点包括:
下面是一个简单的示例代码,演示了闭包的沙箱模式:
function createSandbox() {
var data = 'Secret information';
function privateFunction() {
console.log(data);
}
return privateFunction;
}
// 创建沙箱
var sandbox = createSandbox();
// 调用沙箱内部的私有函数,输出 "Secret information"
sandbox();
在上述示例中,createSandbox
函数返回一个内部的私有函数 privateFunction
。这个内部函数可以访问和操作外部函数中的变量 data
,但其他代码无法直接获取或修改该变量。调用 createSandbox
创建了一个沙箱,并将内部函数赋值给变量 sandbox
。通过调用 sandbox()
可以执行沙箱内部的私有函数并输出结果。
沙箱模式提供了一种隔离代码执行环境的方式,可以保
1.谁作为拥有者调用它就指向谁
function a() {
console.log(this);
}
var b = {};
b.hehe = a;
b.hehe();
//这时候this指向b//常见的就是绑定事件
2.bind谁就指向谁
function a() {
console.log(this);
}
var b = {};
var c = {};
b.hehe = a.bind(c);
b.hehe();
//这时候this指向c//如果你用bind的话
3.没有拥有者,直接调用,就指向window
function a() {
console.log(this);
}
a();
//this指向window
4.call谁就是谁,apply谁就是谁,其实bind就是通过call和apply实现的
箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。浅拷贝只复制对象的第一层属性
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。对对象的属性进行递归复制
1、可以通过简单的赋值实现
类似上面的例子,当然,我们也可以封装一个简单的函数,如下:
function simpleClone(initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
var obj = {
a: "hello",
b:{
a: "world",
b: 21
},
c:["Bob", "Tom", "Jenny"],
d:function() {
alert("hello world");
}
}
var cloneObj = simpleClone(obj);
console.log(cloneObj.b);
console.log(cloneObj.c);
console.log(cloneObj.d);
cloneObj.b.a = "changed";
cloneObj.c = [1, 2, 3];
cloneObj.d = function() { alert("changed"); };
console.log(obj.b);
console.log(obj.c);
console.log(obj.d);
2、Object.assign()实现
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = { a: {a: "hello", b: 21} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"
注意:当object只有一层的时候,是深拷贝,例如如下:
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
1、对象只有一层的话可以使用上面的:Object.assign()函数
2、转成 JSON 再转回来
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
可以封装如下函数
var cloneObj = function(obj){
var str, newobj = obj.constructor === Array ? [] : {};
if(typeof obj !== 'object'){
return;
} else if(window.JSON){
str = JSON.stringify(obj), //系列化对象
newobj = JSON.parse(str); //还原
} else {
for(var i in obj){
newobj[i] = typeof obj[i] === 'object' ?
cloneObj(obj[i]) : obj[i];
}
}
return newobj;
};
3、递归拷贝
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
4、使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
5、jquery
jquery 有提供一个$.extend可以用来做 Deep Copy。
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);
// false
6、lodash
另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false
在 JavaScript 中,1 + 2 === 3
应该返回 true
,因为它是一个等值比较表达式,左边的表达式 1 + 2
的结果是 3
,而右边的表达式 3
是一个数值。
如果这个表达式返回 false
,可能有以下几种可能的原因:
===
会返回 false
。示例:
"3" === 3; // false
示例:
0.1 + 0.2 === 0.3; // false
为了解决这个问题,可以使用舍入函数或者适当的精度处理方法来比较浮点数。
防抖(Debounce): 防抖的原理是,在某个时间段内,如果事件持续触发,则重新计时,直到事件触发停止后再执行最后一次操作。可以通过以下步骤实现防抖功能:
例如,以下是使用 JavaScript 实现防抖的示例代码:
function debounce(func, delay) {
let timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
// 使用防抖包装事件处理函数
const debounceFn = debounce(function() {
// 执行需要防抖处理的操作
}, 300);
// 监听事件,并应用防抖处理
element.addEventListener('input', debounceFn);
节流(Throttle): 节流的原理是,在某个时间段内,无论事件触发多少次,只执行一次操作。可以通过以下步骤实现节流功能:
例如,以下是使用 JavaScript 实现节流的示例代码:
function throttle(func, delay) {
let isThrottled = false;
return function() {
if (isThrottled) {
return;
}
isThrottled = true;
setTimeout(() => {
func.apply(this, arguments);
isThrottled = false;
}, delay);
};
}
// 使用节流包装事件处理函数
const throttleFn = throttle(function() {
// 执行需要节流处理的操作
}, 300);
// 监听事件,并应用节流处理
element.addEventListener('scroll', throttleFn);
通过防抖和节流的应用,可以有效地控制事件的触发频率,避免过多地执行操作,提高页面性能和用户体验。具体选择防抖还是节流要根据具体的场景和需求来决定。
call
, apply
, 和 bind
是 JavaScript 中用于修改函数执行上下文(即 this
的指向)的方法。
call
方法: call
方法允许你调用一个具有指定 this
值和参数的函数。它的基本语法是 function.call(thisArg, arg1, arg2, ...)
,其中:
thisArg
:函数执行时的上下文对象,即函数中的 this
的值。arg1, arg2, ...
:传递给函数的参数列表。call
方法会立即执行函数,并将函数中的 this
设置为 thisArg
,并且可以传入多个参数作为函数的实参。例如:
function greet(name) {
console.log(`Hello, ${name}!`);
console.log(this);
}
const person = {
firstName: 'John',
lastName: 'Doe'
};
greet.call(person, 'Alice');
输出:
Hello, Alice!
{ firstName: 'John', lastName: 'Doe' }
在上述示例中,通过 call
方法将 greet
函数的执行上下文设置为 person
对象,并传入 'Alice'
作为函数的参数。
apply
方法: apply
方法与 call
方法类似,也允许你调用一个具有指定 this
值和参数的函数,但参数传递方式有所不同。它的基本语法是 function.apply(thisArg, [argsArray])
,其中:
thisArg
:函数执行时的上下文对象,即函数中的 this
的值。argsArray
:传递给函数的参数组成的数组。apply
方法会立即执行函数,并将函数中的 this
设置为 thisArg
,并且可以通过数组形式传入参数列表。例如:
function greet(name) {
console.log(`Hello, ${name}!`);
console.log(this);
}
const person = {
firstName: 'John',
lastName: 'Doe'
};
greet.apply(person, ['Alice']);
输出:
Hello, Alice!
{ firstName: 'John', lastName: 'Doe' }
在上述示例中,通过 apply
方法将 greet
函数的执行上下文设置为 person
对象,并使用参数数组 ['Alice']
作为函数的参数。
bind
方法: bind
方法不同于 call
和 apply
方法,它并不立即执行函数,而是创建一个新的绑定函数,可以稍后调用执行。它的基本语法是 function.bind(thisArg, arg1, arg2, ...)
,其中:
thisArg
:函数执行时的上下文对象,即函数中的 this
的值。arg1, arg2, ...
:预先绑定到函数的参数列表。bind
方法返回一个绑定函数,函数中的 this
被设置为 thisArg
,并且可以预先绑定一部分参数。例如:
function greet(name, message) {
console.log(`${message}, ${name}!`);
console.log(this);
}
const person = {
firstName: 'John',
lastName: 'Doe'
};
const sayHello = greet.bind(person, 'Hello');
sayHello('Alice');
输出:
Hello, Alice!
{ firstName: 'John', lastName: 'Doe' }
在上述示例中,通过 bind
方法将 greet
函数的执行上下文设置为 person
对象,并预先绑定 'Hello'
作为函数的第一个参数。之后,创建的绑定
在 JavaScript 中,可以使用 Math.max()
方法或数组的 sort()
方法来获取数组中的最大值。
使用 Math.max()
方法: Math.max()
方法接受一组数字作为参数,并返回其中的最大值。但是,Math.max()
方法不接受数组作为参数,需要使用展开运算符 ...
将数组元素展开为单独的参数传递给该方法。
示例代码如下:
const numbers = [10, 5, 8, 3, 12];
const maxNumber = Math.max(...numbers);
console.log(maxNumber); // 输出:12
使用 sort()
方法: sort()
方法用于对数组进行排序,默认是按照字符编码的顺序排序。通过自定义比较函数,可以实现对数字类型的排序。使用 sort()
方法将数组排序后,最后一个元素即为数组中的最大值。
示例代码如下:
const numbers = [10, 5, 8, 3, 12];
numbers.sort((a, b) => a - b); // 升序排序
const maxNumber = numbers[numbers.length - 1];
console.log(maxNumber); // 输出:12
请注意,使用 sort()
方法会改变原始数组的顺序,如果不想改变原数组,可以先创建副本进行操作。
以上就是获取数组中最大值的方法,根据需求选择合适的方式进行处理。
在 JavaScript 中,.promise
表示一个 Promise 对象的状态。Promise 是一种用于处理异步操作的对象,它可以处于以下三种状态之一:
then()
方法会被调用,并且可以获取到异步操作返回的结果值。catch()
或 finally()
方法会被调用,并且可以处理错误或清理资源。接下来是关于 .all()
和 .race()
方法的说明:
.all()
方法:Promise.all(iterable)
是一个静态方法,用于接收一个可迭代对象(如数组或类数组对象),并返回一个新的 Promise 对象。该新 Promise 对象在所有输入的 Promise 对象都变为 fulfilled 状态时才会变为 fulfilled。如果其中一个 Promise 对象变为 rejected 状态,那么该新的 Promise 对象将立即变为 rejected 状态。示例:
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [1, 2, 3]
})
.catch(error => {
console.error(error);
});
.race()
方法:Promise.race(iterable)
是一个静态方法,同样接收一个可迭代对象,并返回一个新的 Promise 对象。该新 Promise 对象在输入的 Promise 对象中有任意一个变为 fulfilled 或 rejected 状态时就会变为对应的状态。示例:
const promise1 = new Promise((resolve) => setTimeout(resolve, 1000, 'Hello'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 2000, 'World'));
Promise.race([promise1, promise2])
.then(value => {
console.log(value); // 'Hello' (更快完成的 Promise)
})
.catch(error => {
console.error(error);
});
.all()
和 .race()
方法是 Promise 类的静态方法,可以通过 Promise.all()
和 Promise.race()
的方式来调用。
果在使用 Promise.all()
方法时需要对每个 Promise 对象的执行结果进行局部处理,可以将每个 Promise 对象包装在一个函数中,并使用 .map()
方法来创建一个由这些函数包装的 Promise 数组。然后,可以通过在 .then()
回调中处理该数组来获取每个 Promise 的局部结果。
下面是一个示例代码:
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(new Error('Error occurred')),
Promise.resolve(3)
];
const wrappedPromises = promises.map(p => {
return p.then(value => {
// 在这里对每个 Promise 对象的结果进行局部处理
return value * 2; // 这里简单地将结果乘以 2 来演示局部处理
}).catch(error => {
// 在这里对每个 Promise 对象的错误进行局部处理
return error.message; // 这里简单地返回错误消息字符串来演示局部处理错误
});
});
Promise.all(wrappedPromises)
.then(results => {
console.log(results); // [2, 4, 'Error occurred', 6]
})
.catch(error => {
console.error(error);
});
在上述示例中,promises
数组包含了四个 Promise 对象。通过 .map()
方法,我们将每个 Promise 对象包装在一个函数中,并在每个函数中进行局部处理。在这里,我们简单地将 Promise 对象的结果乘以 2 或捕获错误并返回错误消息字符串。最后,使用 Promise.all()
方法来等待所有的 Promise 对象完成,并通过 .then()
回调处理局部结果。
(1):window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。只能有一个
(2)(document).ready() 方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。可以有多个
Ajax
的原理简单来说是在用户和服务器之间加了—个中间层(AJAX
引擎),通过XmlHttpRequest
对象来向服务器发异步请求,从服务器获得数据,然后用javascrip
t来操作DOM
而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据Ajax
的过程只涉及JavaScript
、XMLHttpRequest
和DOM
。XMLHttpRequest
是aja
x的核心机制 //1. 创建连接
var xhr = null;
xhr = new XMLHttpRequest()
// 2. 连接服务器
xhr.open('get', url, true)
// 3. 发送请求
xhr.send(null);
// 4. 接受请求
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
success(xhr.responseText);
} else { // fail
fail && fail(xhr.status);
}
}
}
http://example.com/api?param1=value1¶m2=value2
。而POST请求的参数通常以消息体(payload)的形式发送给服务器,不会直接显示在URL中。1.===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。
例:100===“100” //返回false
abc===“abc” //返回false
‘abc’===“abc” //返回true
NaN===NaN //返回false
false===false //返回true
2.==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。
在React中,useState是一种常用的状态管理钩子,用于在函数组件中声明和更新状态。通常情况下,我们可以使用简单的方式来定义和更新状态,例如:
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
{count}
);
}
这里的useState返回一个由初始值和更新函数组成的数组。然而,你也可以使用闭包来实现useState。以下是使用闭包实现的示例代码:
import React from 'react';
function Example() {
const countState = useClosureState(0);
const increment = () => {
countState.setValue(countState.getValue() + 1);
};
return (
{countState.getValue()}
);
}
// 自定义的闭包状态管理钩子
function useClosureState(initialValue) {
let value = initialValue;
const getValue = () => value;
const setValue = (newValue) => {
value = newValue;
// 触发组件重新渲染
// 此处省略具体的重新渲染逻辑
};
return {
getValue,
setValue
};
}
在上述代码中,我们自定义了一个名为useClosureState的闭包状态管理钩子。通过闭包特性,我们在自定义钩子中创建了一个value变量,并通过getValue和setValue来访问和更新该变量的值。在组件中,我们使用countState来代替useState返回的数组,然后通过countState.getValue()获取状态值,通过countState.setValue()更新状态值。
需要注意的是,闭包方式并不兼容React的更新机制,当调用setValue更新状态值时,组件并不会自动重新渲染。因此,在实际场景中,还需要补充相应的重新渲染逻辑,以确保组件正确显示最新的状态值。
function ajaxRequest(url, method, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = function() {
reject(new Error('Network error'));
};
xhr.send(JSON.stringify(data));
});
}
// 调用示例
ajaxRequest('/api/user', 'GET', { id: 1 })
.then(data => {
console.log('请求成功:', data);
// 在这里处理返回的数据
})
.catch(error => {
console.log('请求失败:', error);
// 在这里处理请求失败的情况
});
在上述示例中,ajaxRequest()
函数使用原生的XMLHttpRequest对象发送请求。它接受三个参数:URL、请求方法和要发送的数据。
在创建XHR对象后,我们设置了请求头并定义了onload
和onerror
事件处理程序。如果请求成功(状态码为2xx),将解析响应文本并通过resolve()
传递给下游的.then()
方法。如果请求失败或出现网络错误,将通过reject()
将错误对象传递给下游的.catch()
方法。
通过使用这种封装,你可以在调用ajaxRequest()
时链式地使用.then()
和.catch()
来处理异步请求的结果。
response.json()
方法来解析响应。在Axios中还可以设置默认的请求头、拦截器等,而Fetch需要手动进行配置。Fetch的优点:
Fetch的缺点:
Axios的优点:
Axios的缺点:
选择使用Fetch还是Axios取决于具体需求和项目环境。如果在现代浏览器中工作且对体积要求较高,可以选择使用Fetch。如果需要更多的功能选项、兼容旧版浏览器或更友好的API,则可以选择Axios。
HTTP和HTTPS本身并不引起跨域问题。跨域问题是由浏览器的同源策略产生的。
同源策略是一种安全机制,限制了来自不同源(协议、域名、端口)的文档或脚本之间的交互。当浏览器执行包含JavaScript的网页时,该策略会阻止对其他源的请求进行读取或操作。
如果一个网页使用HTTP协议加载,而另一个网页使用HTTPS协议加载,则它们被视为不同源。这是因为协议部分不同,符合同源策略的定义。因此,从使用HTTP加载的网页向使用HTTPS加载的网页发送请求,会遇到跨域问题。
为了实现在不同源之间的安全通信,可以通过使用CORS(跨源资源共享)头部信息、代理服务器或者JSONP等技术来克服跨域限制。
需要注意的是,即使两个网页的协议相同,但如果它们的域名或端口号不匹配,仍然被认为是不同源,可能会引发跨域问题。
总结起来,HTTP和HTTPS本身不会导致跨域问题,跨域问题是由浏览器的同源策略引起的。
Access-Control-Allow-Origin
头部字段,将允许访问的域名添加到响应中。同时还可以设置其他相关的头部字段,如Access-Control-Allow-Methods
和Access-Control-Allow-Headers
来控制允许的请求方法和头部信息。标签没有跨域限制的特性,实现跨域请求。通过在页面中动态创建
标签,将远程URL作为其src属性值,并提供一个回调函数作为参数传递到远程服务器。远程服务器在返回数据时,将数据作为参数传递给回调函数,从而实现跨域数据获取。正向代理和反向代理是常用的代理服务器配置方式,它们在应用场景、功能和部署位置上有所不同:
总结: 正向代理和反向代理都是通过代理服务器来实现请求转发和响应处理的。区别在于正向代理面向客户端,隐藏客户端身份; 而反向代理面向内部服务器,隐藏服务器身份。它们在网络安全、负载均衡、加速访问等方面都有重要作用,根据具体需求选择合适的代理方式。
Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
通常情况下有两种实现方式:标记清除和引用计数
当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。
1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:
2)、GC的缺陷
和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。
3)、GC优化策略
事件流(Event Flow)是指在交互式的用户界面中,事件从发生到处理的整个过程。在Web开发中,事件流决定了事件从被触发到最终被处理的路径和顺序。
在传统的事件模型中,存在两种主要的事件流模型:冒泡(Bubbling)和捕获(Capturing)。这两种模型描述了事件在DOM树中的传播方式:
window
对象)。冒泡阶段允许父级元素捕获和处理子元素的事件。window
对象)开始传播,沿着DOM树向下传播,直至到达触发事件的节点。捕获阶段允许更早地捕获和处理事件,但在实践中较少使用。在现代的Web开发中,大多数浏览器都采用了事件委托(Event Delegation)的方式来优化事件处理性能。事件委托利用了事件冒泡的机制,在父级元素上注册事件处理程序,通过冒泡阶段将事件处理委托给子元素,从而减少了重复注册事件处理程序的数量,提高了性能和代码的可维护性。
总结来说,事件流描述了事件在DOM树中从触发到处理的传播过程,可以通过捕获和冒泡阶段进行事件处理,并借助事件委托优化性能。
JavaScript内置对象是指在JavaScript语言中提供的一些预定义对象,可以直接在代码中使用而无需进行额外的声明或导入。以下是一些常见的JavaScript内置对象:
除了以上这些常见的内置对象,JavaScript还提供了其他许多有用的内置对象,如Error(表示错误对象)、Promise(表示异步操作的状态和结果)等。这些内置对象提供了丰富的功能和方法,可以帮助开发者更方便地进行各种操作和处理。
在JavaScript中,原型(prototype)、构造函数(constructor)和实例之间存在着密切的关系。
new
关键字调用,并且在调用时会创建一个新的对象作为其实例。构造函数可以设置实例的初始状态,以及定义实例特有的属性和方法。下面是一个示例代码:
// 构造函数
function Person(name) {
this.name = name;
}
// 在原型对象上定义方法
Person.prototype.sayHello = function() {
console.log("Hello, my name is " + this.name);
};
// 创建实例
var person1 = new Person("Alice");
var person2 = new Person("Bob");
person1.sayHello(); // 输出 "Hello, my name is Alice"
person2.sayHello(); // 输出 "Hello, my name is Bob"
在这个例子中,Person
是一个构造函数,它定义了一个 name
属性和一个 sayHello
方法。通过 new Person("Alice")
创建的对象 person1
是 Person
的一个实例,它具有自己的 name
属性,并可以访问 Person.prototype
上的 sayHello
方法。
综上所述,原型对象存储构造函数的共享属性和方法,构造函数用于创建实例对象,而实例对象通过原型链机制可以访问并继承原型对象的属性和方法。
let
和 const
关键字,允许在块级作用域内声明变量,并且在作用域之外无法访问。this
值。和占位符
${}` 的组合,可以更方便地拼接字符串和插入变量。class
关键字和类的概念,可以更直观地定义对象、原型和类的关系,并通过 extends
实现继承。Map
、Set
、Symbol
等新的数据类型,扩展了 JavaScript 的数据处理能力。for
循环:通过指定初始条件、循环终止条件和递增/递减表达式,来重复执行一段代码块。for (初始化; 终止条件; 递增/递减) {
// 执行的代码块
}
while
循环:只在给定条件为真时重复执行代码块,没有明确的计数器变量。hile (条件) {
// 执行的代码块
}
do...while
循环:先执行一次代码块,然后只要给定条件为真,就重复执行。do {
// 执行的代码块
} while (条件);
for...in
循环:遍历对象的可枚举属性,在每次迭代中将属性赋值给一个变量。for (变量 in 对象) {
// 执行的代码块
}
for...of
循环:以可迭代对象的元素作为循环变量,可以遍历数组、字符串、Set、Map等可迭代的数据结构。for (变量 of 可迭代对象) {
// 执行的代码块
}
生成器和迭代器是 JavaScript 中用于处理可迭代对象的特殊工具。
迭代器(Iterator)是一个对象,它实现了指定的迭代器协议。这个协议包括一个
next()
方法,每次调用该方法时都会返回一个包含value
和done
属性的对象。
value
表示迭代器当前所指向的值。done
是一个布尔值,表示迭代是否已经完成。生成器(Generator)是一个特殊类型的函数,使用
function*
语法声明。生成器函数内部可以通过yield
关键字来暂停函数的执行,并返回一个中间结果。当生成器函数再次被调用时,会从上一次yield
的位置继续执行,直到遇到下一个yield
或函数结束。生成器函数与普通函数的不同之处在于,生成器函数返回的是一个迭代器对象,通过调用生成器函数可以获取到这个迭代器对象。我们可以使用这个迭代器对象按需逐步获取生成器函数中产生的值。
以下是一个简单的示例:
function* generatorFunction() { yield 'Hello'; yield 'World'; yield '!'; } const generator = generatorFunction(); // 获取生成器的迭代器 console.log(generator.next()); // { value: 'Hello', done: false } console.log(generator.next()); // { value: 'World', done: false } console.log(generator.next()); // { value: '!', done: false } console.log(generator.next()); // { value: undefined, done: true }
在上面的示例中,
generatorFunction()
是一个生成器函数,调用它返回一个迭代器对象generator
。通过不断调用generator.next()
方法可以依次遍历生成器函数中产生的值。需要注意的是,当生成器函数执行完毕后再次调用
next()
方法会得到{ value: undefined, done: true }
的结果,表示迭代已经完成。迭代器和生成器提供了一种更灵活和可控的方式来处理可迭代对象,使得我们可以按需获取想要的数据,而无需一次性加载全部数据。
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore()
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
箭头函数(Arrow Function)是 ES6 引入的一种新的函数语法,相比普通函数(也称为命名函数表达式),箭头函数具有以下特点:
普通函数则是传统的 JavaScript 函数定义方式,使用 function 关键字来声明函数。普通函数可以作为构造函数使用(通过 new 运算符实例化对象),并且拥有自己的 this 值和 arguments 对象。
下面是一个使用箭头函数和普通函数实现相同功能的示例:
// 箭头函数
const arrowFunction = (a, b) => {
return a + b;
};
// 普通函数
function normalFunction(a, b) {
return a + b;
}
console.log(arrowFunction(2, 3)); // 输出: 5
console.log(normalFunction(2, 3)); // 输出: 5
需要注意的是,使用箭头函数时需要注意 this 的指向和上下文对象的继承关系。在某些情况下,箭头函数可能会更方便和简洁;而在涉及到动态 this 绑定、原型方法等特殊需求的情况下,普通函数可能更适合使用。
0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用了
使用 alert () 函数来弹出提示框;
使用 confirm () 函数来弹出一个对话框;
使用 document.write () 方法将内容写入到 HTML 文档中;
使用 innerHTML 将内容写入到 HTML 标签中;
使用 console.log () 在浏览器的控制台输出内容。
1.inner HTML
对于inner HTML,w3c给出的解释是:
inner HTML可以写入(改变)HTML元素内容
2.document.write()
对于document.write(),w3c给出的解释是
两者都是对HTML页面进行输出,但大多数情况下优先考虑inner HTML。
两者有主要区别:
inner HTML将内容写入某个DOM节点,document.write()直接输出内容(页面加载时) 或清除整个HTML页面,打开新的页面输出document.write()(页面加载后)
实现一个简单的 JavaScript 轮播图可以按照以下思路进行封装:
transitionEnd事件
ransitionEnd事件会在CSS transition动画结束后触发。
在 JavaScript 中,prototype
和 __proto__
是两个与对象原型(prototype)相关的属性。它们之间有一些区别和关系。
prototype
属性:
prototype
是函数对象特有的属性,用于定义或引用一个对象的原型。prototype
的空对象,并将其赋值给函数的 prototype
属性。prototype
属性,可以添加方法和属性到所有使用该函数创建的对象实例的原型链上。new
关键字调用函数创建新对象时,将该函数的 prototype
对象作为新对象的原型。__proto__
属性:
__proto__
是每个 JavaScript 对象都具有的隐藏属性,用于指向该对象的原型链上的原型(即它的构造函数的 prototype
)。Object.getPrototypeOf(obj)
或 obj.constructor.prototype
来获取对象的原型。__proto__
属性在现代 JavaScript 中被废弃,推荐使用上述的方法来获取对象原型。关系:
__proto__
属性指向对象的原型,即该对象的构造函数的 prototype
属性指向的对象。prototype
和 __proto__
属性共同构成了 JavaScript 对象之间的原型继承机制。简而言之,prototype
是函数对象特有的属性,用于定义对象的原型,而 __proto__
是每个对象都有的属性,用于指向对象的原型链上的原型。
在javascript中,可以使用slice()方法、toUpperCase()方法和toLowerCase()方法来设置首字母大写,确保字符串的首字母都大写,其余部分小写。
步骤:
● 使用slice()方法将字符串分成两部分:首字母字符部分,和其他子字符部分。
● 使用toUpperCase()方法将首字母转换为大写;使用toLowerCase()将其他子字符转换为小写。
● 使用“+”运算符,将两个部分重新拼接起来
function titleCase(str) {
newStr = str.slice(0,1).toUpperCase() +str.slice(1).toLowerCase();
return newStr;
}
titleCase("hello World!");
下面是一个手写的简化版 Promise 的实现,以帮助你理解 Promise 的原理:
class MyPromise {
constructor(executor) {
this.state = 'pending'; // Promise 的初始状态为 pending
this.value = undefined; // Promise 的最终结果或错误信息
this.callbacks = []; // 存储回调函数的数组
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'; // 状态变为 fulfilled
this.value = value; // 存储结果值
this.executeCallbacks(); // 执行所有成功的回调函数
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'; // 状态变为 rejected
this.value = reason; // 存储错误信息
this.executeCallbacks(); // 执行所有失败的回调函数
}
};
try {
executor(resolve, reject); // 执行执行器函数,并传入 resolve 和 reject 函数
} catch (error) {
reject(error); // 如果执行器函数抛出异常,则将 Promise 状态设为 rejected
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const callback = {
onFulfilled,
onRejected,
resolve,
reject
};
// 根据 Promise 当前的状态,决定是立即执行回调还是存储到数组中待执行
if (this.state === 'fulfilled') {
this.executeCallback(callback);
} else if (this.state === 'rejected') {
this.executeCallback(callback);
} else {
this.callbacks.push(callback);
}
});
}
executeCallbacks() {
this.callbacks.forEach((callback) => {
this.executeCallback(callback);
});
this.callbacks = []; // 清空回调函数数组
}
executeCallback(callback) {
const { onFulfilled, onRejected, resolve, reject } = callback;
if (this.state === 'fulfilled') {
if (typeof onFulfilled === 'function') {
try {
const result = onFulfilled(this.value); // 执行成功的回调函数
resolve(result); // 将成功结果传递给下一个 Promise
} catch (error) {
reject(error); // 如果回调函数抛出异常,则将 Promise 状态设为 rejected
}
} else {
resolve(this.value); // 如果没有成功的回调函数,则直接将结果传递给下一个 Promise
}
} else if (this.state === 'rejected') {
if (typeof onRejected === 'function') {
try {
const result = onRejected(this.value); // 执行失败的回调函数
resolve(result); // 将成功结果传递给下一个 Promise
} catch (error) {
reject(error); // 如果回调函数抛出异常,则将 Promise 状态设为 rejected
}
} else {
reject(this.value); // 如果没有失败的回调函数,则直接将错误信息传递给下一个 Promise
}
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
这个简化版的 Promise 实现了基本的异步处理和链式调用的功能。它可以通过 new MyPromise(executor)
来创建一个 Promise 对象,其中 executor
是一个函数,它接受两个参数 resolve
和 reject
。在执行器函数中,你可以调用 resolve(value)
来表示成功,并传递一个结果值给下一个 Promise;调用 reject(reason)
来表示失败,并传递一个错误信息给下一个 Promise。
Promise 的 then
方法用于注册成功和失败的回调函数,并返回一个新的 Promise 对象,以
for in
更适合遍历对象,当然也可以遍历数组,但是会存在一些问题,
for of
遍历的是数组元素值,而且for of
遍历的只是数组内的元素,不包括原型属性和索引
for in
遍历的是数组的索引(即键名),而for of
遍历的是数组元素值
for in
总是得到对象的key
或数组、字符串的下标
for of
总是得到对象的value
或数组、字符串的值
值 null
是一个字面量,不像 undefined
,它不是全局对象的一个属性。null
是表示缺少的标识,指示变量未指向任何对象。把 null
作为尚未创建的对象,也许更好理解。在 API 中,null
常在返回类型应是一个对象,但没有关联的值的地方使用。
undefined
是 全局对象 的一个属性。也就是说,它是全局作用域的一个变量。undefined
的最初值就是原始数据类型 undefined
。
bind、call 和 apply 都是用来改变函数中 this 指向的方法。
其中,call 和 apply 的作用相同,都是立即调用函数,只是传参的方式略有不同:
call(fn, arg1, arg2, …): 将函数以指定的对象(也就是 this)调用,并且把函数参数逐个列举出来
apply(fn, [argsArray]): 将函数以指定的对象(也就是 this)调用,并且把函数的参数放到一个数组中作为参数传入
而 bind 则不是立即执行函数,而是创建一个新函数,将原函数中的 this 绑定到指定对象上,并返回一个新的函数。新函数被调用时,它会以绑定的 this 对象作为上下文对象,以原函数的形式被调用,后面加上绑定的参数。
实现一个 bind 方法的思路如下:
返回一个函数。
函数内部使用 apply 或 call 改变函数执行时的 this 值,同时参数也要和绑定函数保持一致。
考虑到返回的函数可能存在构造函数的情况,需要判断绑定函数是否是用 new 关键字进行实例化的,如果是则返回当前绑定函数的实例,否则返回所需函数的执行结果。
代码实现如下:
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError("只有函数才能调用 bind 方法");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function() {}
var fBound = function() {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
这样我们就可以使用 myBind 来代替原生的 bind 方法了。