JavaScript 高级特性
原型与原型链
new 操作符的实现
new 操作符具体干了什么?
- 创建一个空对象,将它的引用赋给 this,继承函数的原型
- 通过 this 将属性和方法添加至这个对象
- 最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象)
手写 new 实现:
javascript
function myNew(constructor, ...args) {
// 1. 创建一个空对象,原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 执行构造函数,绑定 this
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回了对象,则返回该对象,否则返回新创建的对象
return result instanceof Object ? result : obj;
}
// 使用示例
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = myNew(Person, '张三', 25);
console.log(person); // Person { name: '张三', age: 25 }instanceof 原理
原理: 通过判断对象的原型链中是否存在指定构造函数的原型。
主要用于判断引用类型的数据,原始数据类型(如字符串、数字、布尔值)则无法准确判断。
手写 instanceof:
javascript
function myInstanceof(obj, constructor) {
// 获取对象的原型
let proto = Object.getPrototypeOf(obj);
// 获取构造函数的 prototype
const prototype = constructor.prototype;
// 沿着原型链查找
while (proto) {
if (proto === prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 使用示例
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof([], Object)); // true
console.log(myInstanceof({}, Array)); // false字面量创建对象和 new 创建对象的区别
字面量创建:
javascript
const obj = { name: '张三' };
// 原型链:obj -> Object.prototype -> nullnew 创建:
javascript
function Person(name) {
this.name = name;
}
const obj = new Person('张三');
// 原型链:obj -> Person.prototype -> Object.prototype -> nullObject.create(null) 创建:
javascript
const obj = Object.create(null);
obj.name = '张三';
// 原型链:obj -> null(没有原型链)区别:
- 字面量和 new 出来的对象有原型链,可以访问继承的属性和方法
- Object.create(null) 创建的对象没有原型链,无法访问任何继承的属性或方法
- 适用于创建纯净的对象,作为字典使用
作用域与闭包
闭包
什么是闭包?
闭包是指有权访问另一个函数作用域中变量的函数。
应用场景:
- 模块模式(私有变量)
javascript
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.getCount()); // 1- 函数柯里化
javascript
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6- 防抖和节流
javascript
// 防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}异步编程
宏任务和微任务
宏任务(Macro Task):
- script(整体代码)
- setTimeout
- setInterval
- setImmediate(Node.js)
- I/O
- UI Rendering
微任务(Micro Task):
- Promise
- process.nextTick(Node.js)
- MutationObserver
执行顺序:
- 执行宏任务 script
- 进入 script 后,所有的同步任务主线程执行
- 所有宏任务放入宏任务执行队列
- 所有微任务放入微任务执行队列
- 先清空微任务队列
- 再取一个宏任务,执行,再清空微任务队列
- 依次循环
示例:
javascript
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// 输出顺序:1, 4, 3, 2Promise 实现
手写 Promise:
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
}设计模式
单例模式
javascript
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true观察者模式
javascript
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
console.log('Received data:', data);
}
}
// 使用示例
const subject = new Subject();
const observer = new Observer();
subject.addObserver(observer);
subject.notify('Hello'); // Received data: Hello发布订阅模式
javascript
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(...args));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 使用示例
const emitter = new EventEmitter();
const callback = (data) => console.log('Received:', data);
emitter.on('message', callback);
emitter.emit('message', 'Hello'); // Received: Hello其他重要概念
requestAnimationFrame
什么是 requestAnimationFrame?
requestAnimationFrame 请求数据帧可以用做动画执行。
特点:
- 可以自己决定什么时机调用该回调函数
- 能保证每次屏幕刷新的时候只被执行一次
- 页面被隐藏或者最小化的时候暂停执行,返回窗口继续执行,有效节省 CPU
使用示例:
javascript
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);URL 编码
encodeURIComponent 和 encodeURI 的区别:
- encodeURI:用于处理整个 URI,不会转义
&,?,/,=等功能字符 - encodeURIComponent:用于编码 URI 中的值,会转义所有特殊字符
javascript
const url = 'https://example.com?name=张三&age=25';
console.log(encodeURI(url));
// https://example.com?name=%E5%BC%A0%E4%B8%89&age=25
console.log(encodeURIComponent(url));
// https%3A%2F%2Fexample.com%3Fname%3D%E5%BC%A0%E4%B8%89%26age%3D25为什么需要编码?
URL 地址携带的参数经过编码是为了确保参数值中的特殊字符不会影响 URL 的解析和传输。特殊字符如空格、&、#、% 等在 URL 中具有特殊含义,如果不经过编码,可能会导致 URL 解析错误或者参数传递错误。
