JS高級知識入門教程
本文深入探讨了JS高级数据类型与对象,包括基本类型和引用类型的详细解释及操作示例。文章进一步介绍了JS高级函数特性,如闭包、高阶函数及Promise与async/await的使用。此外,还涵盖了面向对象编程、调试与错误处理以及异步编程的相关知识,旨在全面提高对JS高级知识的理解和应用。
JS高级数据类型与对象JS中的基本数据类型与引用类型
在JavaScript中,数据可以分为两种类型:基本类型和引用类型。基本类型包括Number
、String
、Boolean
、Null
、Undefined
、Symbol
,引用类型则是Object
,包括数组(Array)、日期(Date)、函数(Function)等。
基本类型
基本类型存储在栈中,直接存储数据值,数据的复制是值的复制。例如:
let x = 1;
let y = x; // y将值1赋值给x
x = 2;
console.log(x); // 输出:2
console.log(y); // 输出:1
在这个例子中,x
和y
都是Number
类型的基本类型,当x
的值被修改为2时,y
的值没有改变,仍然是1。
引用类型
引用类型存储在堆中,存储的是指向对象的指针,数据的复制是引用的复制。例如:
let x = [1, 2, 3];
let y = x; // y指向x的地址
x.push(4);
console.log(x); // 输出:[1, 2, 3, 4]
console.log(y); // 输出:[1, 2, 3, 4]
在这个例子中,x
和y
都指向同一个数组对象,当x
的数组添加一个元素后,y
也指向了包含新元素的数组。
对象的创建与操作
在JavaScript中,对象可以通过字面量、构造函数等方式创建。同时,对象可以通过点操作符.
或者方括号操作符[]
来访问和修改属性值。
对象字面量
使用字面量创建对象是最简单直接的方式。
let person = {
name: "张三",
age: 25,
greet: function() {
console.log("Hello, my name is " + this.name);
}
};
person.greet(); // 输出:"Hello, my name is 张三"
构造函数
构造函数是一种特殊类型的函数,用于创建对象实例。
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log("Hello, my name is " + this.name);
}
}
let person = new Person("张三", 25);
person.greet(); // 输出:"Hello, my name is 张三"
在使用构造函数时,必须使用关键字new
来创建对象实例。
ES6新数据类型介绍(Set、Map等)
ES6引入了新的数据结构Set
和Map
,提供了更高效的数据操作。
Set
Set
是一种集合数据结构,集合内的元素是唯一的。
let set = new Set();
set.add(1); // 添加元素
set.add(2);
set.add(3);
set.add(1); // 重复添加,不会重复添加
console.log(set.size); // 输出:3
console.log(set.has(1)); // 输出:true
set.delete(1); // 删除元素
console.log(set.has(1)); // 输出:false
更复杂的使用场景:
let set = new Set([1, 2, 3, 3, 1]);
console.log(set); // Set(3) {1, 2, 3}
Map
Map
是一种键值对集合,相比Object
,Map
可以使用任意类型的键,包括对象。
let map = new Map();
map.set("name", "张三");
map.set("age", 25);
console.log(map.get("name")); // 输出:"张三"
console.log(map.size); // 输出:2
map.delete("age"); // 删除键值对
console.log(map.size); // 输出:1
更复杂的使用场景:
let map = new Map();
map.set("name", "张三");
map.set("age", 25);
console.log(map.get("name")); // 输出:"张三"
console.log(map.size); // 输出:2
map.delete("age"); // 删除键值对
console.log(map.size); // 输出:1
JS高级函数特性
函数作为第一类对象
在JavaScript中,函数是第一类对象,可以像普通对象一样赋值给变量、作为参数传递给函数、从其他函数返回,甚至可以作为另一个函数的属性。
function greet(name) {
return "Hello, " + name;
}
let greetFunction = greet;
console.log(greetFunction("张三")); // 输出:"Hello, 张三"
let functions = [greet, greetFunction];
console.log(functions[0]("张三")); // 输出:"Hello, 张三"
闭包的概念与应用
闭包是JavaScript的一个重要特性,涉及到函数内部的词法环境。当一个函数被返回,并且返回的函数引用了外部函数的局部变量,那么这个返回的函数就是一个闭包。
function createCounter() {
let count = 0; // 局部变量
function counter() {
count++;
return count;
}
return counter;
}
let counter = createCounter();
console.log(counter()); // 输出:1
console.log(counter()); // 输出:2
更复杂的应用场景:
function createCounter() {
let count = 0;
function counter() {
count++;
return count;
}
return counter;
}
let counter = createCounter();
console.log(counter()); // 输出:1
console.log(counter()); // 输出:2
高阶函数与回调函数详解
高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。回调函数是作为参数传递给其他函数的函数,通常在某个事件触发时执行。
function executeAsync(callback) {
setTimeout(function() {
console.log("异步执行完成");
callback(); // 执行回调函数
}, 1000);
}
executeAsync(function() {
console.log("回调执行");
}); // 输出:"异步执行完成",然后输出:"回调执行"
更复杂的应用场景:
function executeAsync(callback) {
setTimeout(() => {
console.log("异步执行完成");
callback();
}, 1000);
}
executeAsync(() => {
console.log("回调执行");
});
promise与async/await使用
Promise是ES6引入的新特性,用于处理异步操作的完成状态和结果。async/await
是对Promise的语法糖,使得异步代码看起来更像同步代码。
Promise
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("异步操作完成");
}, 1000);
});
promise.then(result => {
console.log(result); // 输出:"异步操作完成"
});
更复杂的用例:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("异步操作完成");
}, 1000);
});
promise.then(result => {
console.log(result); // 输出:"异步操作完成"
});
async/await
async function fetchAsync() {
let result = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("异步操作完成");
}, 1000);
});
console.log(result); // 输出:"异步操作完成"
}
fetchAsync();
更复杂的用例:
async function fetchAsync() {
let result = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("异步操作完成");
}, 1000);
});
console.log(result); // 输出:"异步操作完成"
}
fetchAsync();
JS面向对象编程
类与原型链的理解
在ES5中,原型链用于实现继承关系,每个构造函数都有一个prototype
属性,指向一个原型对象。在ES6中,引入了新的class
语法,使得面向对象编程更加简洁。
ES5原型链
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("Hello, " + this.name);
};
let person = new Person("张三");
person.greet(); // 输出:"Hello, 张三"
ES6类
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
let person = new Person("张三");
person.greet(); // 输出:"Hello, 张三"
更复杂的应用场景:
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
let person = new Person("张三");
person.greet(); // 输出:"Hello, 张三"
继承的实现方式对比
在ES5中,继承通常通过原型链实现,或者使用构造函数委托。在ES6中,使用class
关键字可以更直观地实现继承。
ES5原型链继承
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("Hello, " + this.name);
};
function Student(name, grade) {
Person.call(this, name); // 调用父构造函数
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype); // 继承原型
Student.prototype.constructor = Student; // 修复构造函数
let student = new Student("李四", "一年级");
student.greet(); // 输出:"Hello, 李四"
console.log(student.grade); // 输出:"一年级"
更复杂的应用场景:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("Hello, " + this.name);
};
function Student(name, grade) {
Person.call(this, name); // 调用父构造函数
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype); // 继承原型
Student.prototype.constructor = Student; // 修复构造函数
let student = new Student("李四", "一年级");
student.greet(); // 输出:"Hello, 李四"
console.log(student.grade); // 输出:"一年级"
ES6类继承
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
class Student extends Person {
constructor(name, grade) {
super(name); // 调用父构造函数
this.grade = grade;
}
study() {
console.log(this.name + "正在学习");
}
}
let student = new Student("李四", "一年级");
student.greet(); // 输出:"Hello, 李四"
console.log(student.grade); // 输出:"一年级"
student.study(); // 输出:"李四正在学习"
更复杂的应用场景:
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log("Hello, " + this.name);
}
}
class Student extends Person {
constructor(name, grade) {
super(name); // 调用父构造函数
this.grade = grade;
}
study() {
console.log(this.name + "正在学习");
}
}
let student = new Student("李四", "一年级");
student.greet(); // 输出:"Hello, 李四"
console.log(student.grade); // 输出:"一年级"
student.study(); // 输出:"李四正在学习"
ES6类的使用
ES6的类是对ES5原型链的语法糖,提供了更简洁的继承和类的定义方式。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
let rectangle = new Rectangle(3, 4);
console.log(rectangle.getArea()); // 输出:12
更复杂的应用场景:
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
getPerimeter() {
return 2 * (this.width + this.height);
}
}
let rectangle = new Rectangle(3, 4);
console.log(rectangle.getArea()); // 输出:12
console.log(rectangle.getPerimeter()); // 输出:14
JS调试与错误处理
常见的调试工具介绍
JavaScript的调试通常使用浏览器内置的开发者工具,如Chrome的DevTools。开发者工具提供了一系列工具,包括源代码查看、断点设置、变量查看等。
使用Chrome DevTools
- 打开Chrome浏览器,访问任意页面。
- 按F12或者右键点击页面元素,选择"检查"。
- 在开发者工具中,选择"源代码"标签,可以查看当前页面的源代码。
- 在源代码中设置断点,执行到断点位置时会暂停执行,可以查看当前的变量值。
错误对象与try-catch语句
JavaScript中的错误处理通常使用try-catch
语句,捕获异常并进行处理。Error
对象提供了错误信息。
try {
let result = null;
console.log(result.someProperty); // 引发TypeError
} catch (error) {
console.log("发生错误:" + error.message); // 输出:"发生错误:Cannot read property 'someProperty' of null"
}
更复杂的应用场景:
try {
let result = null;
console.log(result.someProperty); // 引发TypeError
} catch (error) {
console.log("发生错误:" + error.message); // 输出:"发生错误:Cannot read property 'someProperty' of null"
}
调试技巧与最佳实践
- 使用
console.log
输出变量值,查看变量的状态。 - 在开发者工具中使用断点,查看执行流程。
- 逐步执行代码,观察每一个变量的变化。
- 使用
debugger
关键字,插入断点,控制代码的执行。
更复杂的应用场景:
let num = 0;
function increment() {
num++;
console.log("当前值:" + num);
}
increment(); // 输出:"当前值:1"
increment(); // 输出:"当前值:2"
JS异步编程
同步与异步的区别
同步操作是阻塞式的,一个操作完成之后才能进行下一个操作。异步操作是非阻塞式的,可以同时执行多个操作。
同步示例
console.log("开始");
console.log("操作1");
console.log("完成");
异步示例
console.log("开始");
setTimeout(function() {
console.log("操作1");
}, 1000);
console.log("完成");
更复杂的应用场景:
console.log("开始");
setTimeout(function() {
console.log("操作1");
}, 1000);
console.log("完成");
setTimeout(function() {
console.log("操作2");
}, 2000);
简单的回调函数与回调地狱
回调函数是一种处理异步操作的方式,但是过多的嵌套会导致回调地狱。
回调地狱示例
setTimeout(function() {
console.log("操作1");
setTimeout(function() {
console.log("操作2");
setTimeout(function() {
console.log("操作3");
}, 1000);
}, 1000);
}, 1000);
更复杂的应用场景:
setTimeout(function() {
console.log("操作1");
setTimeout(function() {
console.log("操作2");
setTimeout(function() {
console.log("操作3");
}, 1000);
}, 1000);
}, 1000);
Promise与async/await的使用场景
Promise和async/await
是处理异步操作的更好方式,使得代码更清晰。
Promise示例
let promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("操作1");
}, 1000);
});
promise1.then(function(result) {
console.log(result); // 输出:"操作1"
return result + "操作2";
}).then(function(result) {
console.log(result); // 输出:"操作1操作2"
});
更复杂的用例:
let promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("操作1");
}, 1000);
});
promise1.then(function(result) {
console.log(result); // 输出:"操作1"
return result + "操作2";
}).then(function(result) {
console.log(result); // 输出:"操作1操作2"
});
async/await示例
async function fetchAsync() {
let result = await new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("操作1");
}, 1000);
});
console.log(result); // 输出:"操作1"
return result + "操作2";
}
fetchAsync().then(function(result) {
console.log(result); // 输出:"操作1操作2"
});
更复杂的用例:
async function fetchAsync() {
let result = await new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("操作1");
}, 1000);
});
console.log(result); // 输出:"操作1"
return result + "操作2";
}
fetchAsync().then(function(result) {
console.log(result); // 输出:"操作1操作2"
});
性能优化与最佳实践
性能优化常用方法
- 减少DOM操作次数,减少页面重绘和重排。
- 使用事件代理,减少事件绑定。
- 使用
requestAnimationFrame
进行动画。 - 使用
Image
对象预加载图片。
减少DOM操作次数
let div = document.getElementById("myDiv");
let content = "";
for (let i = 0; i < 100; i++) {
content += "<p>内容" + i + "</p>";
}
div.innerHTML = content; // 整体更新DOM节点
更复杂的应用场景:
let div = document.getElementById("myDiv");
let content = "";
for (let i = 0; i < 100; i++) {
content += "<p>内容" + i + "</p>";
}
div.innerHTML = content; // 整体更新DOM节点
使用事件代理
document.getElementById("parent").onclick = function(event) {
if (event.target.tagName === "A") {
console.log("点击了链接");
}
};
更复杂的应用场景:
document.getElementById("parent").onclick = function(event) {
if (event.target.tagName === "A") {
console.log("点击了链接");
}
};
代码优化技巧
- 使用严格模式,避免隐式类型转换等潜在问题。
- 避免全局变量,使用模块化的方式组织代码。
- 使用缓存,减少重复计算。
- 使用惰性加载,按需加载代码。
使用严格模式
"use strict";
let variable = 1;
console.log(variable); // 输出:1
更复杂的应用场景:
"use strict";
let variable = 1;
console.log(variable); // 输出:1
避免内存泄漏的方法
内存泄漏主要发生在引用不再需要的对象。常见的内存泄漏原因包括闭包、全局变量、未清除的定时器等。
解决内存泄漏的示例
function createLeak() {
let intervalId = setInterval(function() {
console.log("内存泄漏");
}, 1000);
// 清除定时器
setTimeout(function() {
clearInterval(intervalId);
}, 10000);
}
createLeak();
更复杂的应用场景:
function createLeak() {
let intervalId = setInterval(function() {
console.log("内存泄漏");
}, 1000);
// 清除定时器
setTimeout(function() {
clearInterval(intervalId);
}, 10000);
}
createLeak();
共同學習,寫下你的評論
評論加載中...
作者其他優質文章