Skip to content

前端面试-笔记1

严格模式"use strict"

"use strict"必须在最前位置 全局执行指向全局对象 函数内部取决于被调用方式

  • 函数内部直接调用this
    • 非严格模式:全局对象(window)
    • 严格模式:undefined
  • 对象方法调用this
    • 指向调用者

示例全局执行

打印全局对象,在浏览器环境下打印window

javascript
"use strict"
console.log(this)

this指向


直接调用

非严格模式

javascript
function fn(){
  console.log(this)
}
fu()

打印输出window

严格模式

javascript
"use strict"
function fn(){
  console.log(this)
}
fu()

打印输出undefined

对象方法调用

this始终指向调用者

非严格模式

javascript
const food = {
  name = '苹果',
  eat(){
    console.log(this)
  }
}

打印food对象 { name:'苹果', eat:f }

严格模式

javascript
"use strict"
const food = {
  name = '苹果',
  eat(){
    console.log(this)
  }
}

打印food对象 { name:'苹果', eat:f }

指定this的值

调用时指定

call方法和apply方法

apply 和 call 是 JavaScript 中用于调用函数的两个方法,它们的主要作用是允许你显式地指定函数内部的 this 值,并传递参数给函数。尽管它们的目的相同,但在参数传递的方式上有所不同。

call 方法: 接受一个参数列表(以逗号分隔的形式直接传递)。

apply 方法: 接受一个数组(或类数组对象)作为参数列表。

call方法

javascript
fnc.call(thisArg, 参数, 参数...)

示例

javascript
function fnc(num1, num2){
  console.log(this)
  console.log(num1 , num2)
}
const person = {
  name:'张三'
}

fnc.call(person, 1, 2)

打印输出{name:'张三'} 1 2

apply方法

以数组形式传入参数

javascript
fnc.call(thisArg, [参数, 参数...])

示例

javascript
function fnc(num1, num2){
  console.log(this)
  console.log(num1 , num2)
}
const person = {
  name:'李四'
}

fnc.apply(person, [34])

打印输出{name:'李四'} 3 4

创建时指定

bind方法和箭头函数

bind方法

bind 方法在 JavaScript 中用于创建一个新的函数,当这个新函数被调用时,它的 this 关键字会被设置为传给 bind() 的第一个参数,同时还可以预先指定部分或全部参数。这是 JavaScript 函数式编程中非常有用的特性之一,特别是在需要固定函数内部的 this 指向或者部分应用(partial application)函数参数的情况下。

javascript
const bindFunc = func.bind(thisArg,参数1 ,参数2...)

示例1

javascript
function fnc(num1, num2){
  console.log(this)
  console.log(num1 , num2)
}
const person = {
  name:'张三'
}

const bindFunc = fnc.bind(person, 666)

不会打印出任何东西,只有在调用时才会打印出结果

示例2

javascript
function fnc(num1, num2){
  console.log(this)
  console.log(num1 , num2)
}
const person = {
  name:'张三'
}

const bindFunc = fnc.bind(person, 114)//作为第一个参数
bindFunc(514)//作为第二个参数

打印输出{name:'张三'} 114 514

示例3

javascript
function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

// 创建一个新的函数,`this` 被绑定到 `person`,并预填充参数
const greetAlice = greet.bind(person, 'Hello');

// 调用新函数
greetAlice('!');

打印 Hello, Alice!

箭头函数

由上一级作用域的this决定

示例1不使用箭头函数

javascript
const person = {
  name:'张三',
  myself(){
    console.log(this)
    setTimeout(function(){
    console.log(this)
    },1000)
  }
}
person.myself()

打印输出{name:'张三'} 一秒后全局对象window

示例2使用箭头函数

javascript
const person = {
  name:'张三',
  myself(){
    console.log(this)

    setTimeout(()=>{
      console.log(this) 
    },1000)
    // setTimeout(function(){
    // console.log(this)
    // },1000)
  }
}
person.myself()

打印输出{name:'张三'} 一秒后

手写call、apply、bind

myCall 实现 call方法

定义mycall方法、设置this调用原函数、接受剩余参数返回结果。

javascript
  console.log('myCall执行了')
//1.定义myCall方法
//3.接收剩余参数并返回结果(...args)

  /*Function 是function的原型对象 
  /Function.prototype)添加一个自定义方法
  /这个方法将会成为所有函数的共享方法,因为所有的函数都继承自 Function.prototype。
 */
  //thisArg为调用者(原函数的this对象
  //args为剩余参数 (Rest参数)它会把参数列表中剩余的参数收集到一个数组中。
Function.prototype.myCall = function(thisArg, ...args){
  //2.设置this调用原函数
  //thisArg 为调用者,this为原函数
  //this 原函数(原函数.mycall)
  thisArg.f = this
  //调用原函数,并传入剩余参数
  
  const res = thisArg.f(...args)//...spread运算符  把一个数组展开为列表
  //删除f属性
  delete thisArg.f
  console.log('')
  console.log('myCall执行完毕')
  return res
}


//-------------测试代码------------
const person = {
  name:'张三'
}
funcation fnc(num1, num2){
  console.log(this)
  console.log(num1 , num2)
  return num1 + num2
}

const res = func.myCall(person, 1, 2)
console.log('返回值是:'res)

//-------------测试代码2------------

const food = {
  name:'苹果',
}

function func2(num1, num2, nim3){
  console.log(this)
  console.log(num1 , num2, num3)
  return num1 + num2 + num3

}

const res2 = func2.myCall(food, 1, 2, 3)
console.log('返回值是:'res2)

打印输出以测试2为例:

this 为food对象,num1为1,num2为2,num3为3 res2为6

这段代码调用时将func2的this对象更改为 food对象,并传入参数1,2,3,返回结果6

myCall 实现 (symbol优化代码)

代码隐患:

无法保证f一定是不同名称的属性,所以导致原f属性被覆盖。

javascript
  thisArg.f = this

解决方法给thisArg加一个一定不重名的属性名保证f一定是不同名称的属性(方法)。

解决方案es6 symbol优化代码

在 JavaScript 中,symbol 是一种原始数据类型(primitive data type),用于创建唯一的标识符。它是 ES6(ECMAScript 2015)引入的一个新特性,主要用于解决对象属性名冲突的题。

Symbol 的一个重要特性:唯一性。即使两个 Symbol 创建时传入了相同的描述字符串,它们仍然是不同的值。下面我将详细解读这段代码,并解释为什么会有这样的行为。

javascript
const a = Symbol();
const b = Symbol();
console.log(a === b);
//false
  1. const a = Symbol(); const b = Symbol(); 这里创建了两个 Symbol 值 a 和 b。 即使没有传入任何描述字符串,Symbol() 每次调用都会返回一个全新的、唯一的值。 因此,a 和 b 是两个完全不同的值,即使它们看起来没有任何区别。

  2. console.log(a === b); === 是严格相等运算符,用于比较两个值是否完全相同。 因为 a 和 b 是两个独立的 Symbol 值,所以它们不相等,结果为 false。

javascript
const c = Symbol('111');
const d = Symbol('111');
console.log(c === d);
//false

3.const c = Symbol('111'); const d = Symbol('111'); 这里创建了两个新的 Symbol 值 c 和 d,并分别传入了相同的描述字符串 '111'。 尽管描述字符串相同,Symbol('111') 每次调用仍然会返回一个全新的、唯一的值。 因此,c 和 d 也是两个完全不同的值。

  1. console.log(c === d); 同样地,c 和 d 是两个独立的 Symbol 值,因此它们不相等,结果为 false。

为什么 Symbol 总是唯一的?

Symbol 的设计初衷就是为了提供一种独一无二的标识符,以避免命名冲突。以下是关键点:

每次调用 Symbol() 都生成一个新的值: 不管是否传入描述字符串,Symbol() 每次调用都会创建一个全新的值。 描述字符串仅仅是一个可选的标记,用于调试或说明用途,但它不会影响 Symbol 的唯一性。

描述字符串的作用: 描述字符串(如 '111')只是为了方便开发者阅读和调试,它并不参与 Symbol 的值比较。 描述相同的 Symbol 值仍然是不同的,因为它们本质上是独立的标识符。

唯一性保证: Symbol 的唯一性是由 JavaScript 引擎在底层实现的,确保每个 Symbol 值都不会与其他值相等。 这种特性使得 Symbol 非常适合用作对象的属性名,避免与现有属性名发生冲突。

symbol优化代码

javascript
/**
 * myCall 实现 call方法
 * 1. 定义myCall方法
 * 2. 设置this调用原函数
 * 3. 接收剩余参数并返回结果(...args)
 * 4.使用symbiol优化代码
 */
//1.定义myCall方法
//3.接收剩余参数并返回结果(...args)
Function.prototype.myCall = function(thisArg, ...args){
  //2.设置this调用原函数
  //4.使用symbiol优化代码
  //直接定义symbol是内置的符号
  const sym = Symbol('sym')
  //使用[]解析为属性名   而不是.语法
  thisArg[sym]= this
  const res = thisArg[sym](...args)
  delete thisArg[sym]
  console.log('')
  return res
}
//-------------测试代码------------

const food = {
  name:'苹果',
}

function func2(num1, num2, nim3){
  console.log(this)
  console.log(num1 , num2, num3)
  return num1 + num2 + num3

}

const res2 = func2.myCall(food, 1, 2, 3)
console.log('返回值是:'res2)

打印输出以测试2为例:{name:'苹果'} 1 2 3 6

myapply 实现 apply方法

apply方法与call方法类似,只是apply方法接收一个数组作为参数。

javascript
/**
 * myApply 实现 apply方法
 * 1. 定义myApply方法
 * 2. 设置this调用原函数
 * 3. 接收剩余参数并返回结果(...args)
 * 4.使用symbol优化代码
 */
//1.定义myApply方法
//3.接收剩余参数并返回结果(...args)
Function.prototype.myApply = function(thisArg, args){
  //2.设置this调用原函数
  const sym = Symbol('sym')
  //使用[]解析为属性名   而不是.语法
  thisArg[sym]= this
  const res = thisArg[sym](...args)
  delete thisArg[sym]
  console.log('')
  return res
}
//-------------测试代码------------

const food = {
  name:'苹果',
}

function func2(num1, num2, nim3){
  console.log(this)
  console.log(num1 , num2, num3)
  return num1 + num2 + num3

}

const res = func2.myCall(food, [1, 2, 3])
console.log('返回值是:'res)

打印结果输出以测试2为例:{name:'苹果'} [1, 2, 3] 6

myBind 实现 bind方法

同上述相同,只是返回一个函数,调用时再执行原函数。

javascript
/**
 * myBind 实现 bind方法
 * 1. 定义myBind方法
 * 2. 设置this调用原函数
 * 3. 接收剩余参数并返回结果(...args)
 * 4.使用symbol优化代码
 */
//1.定义myBind方法
Function.prototype.myBind= function(thisArg, ...args){
  //2.返回绑定this的新函数
  return (...reArgs) => {
    //this:指向原函数(myBind)
    return this.call(thisArg, ...args , ...reArgs)
  }
}

//-------------测试代码------------

const food = {
  name:'苹果',
}

function func(num1, num2, nim3,num4){
  console.log(this)
  console.log(num1 , num2, num3,num4)
  return num1 + num2 + num3 + num4

}

const bindFunc = func.myBind(food, 1,2)

comsst res = bindFunc(3,4)
console.log('返回值是:'res)

打印结果s输出以测试2为例:{name:'苹果'} 1 2 3 4 10

Javascript 继承

继承: 继承可以使子类服用父类的属性和方法,从而减少代码重复。

继承是面向对象编程(OOP)中的一个重要概念,它允许一个对象从另一个对象继承属性和方法。在 JavaScript 中,继承是通过原型链实现的。

继承的实现方式

在 JavaScript 中,继承是一种机制,允许一个对象或类基于另一个对象或类来创建。这种机制使得子类可以复用父类的属性和方法,并且可以根据需要添加新的功能或重写已有的功能。JavaScript 实现继承的方式主要依赖于原型链(prototype chaining)。

ES6 基于Class实现继承

ES6 引入了 class 语法糖,使继承的实现更加直观和简洁。

class核心语法

类是用于创建对象的模板,它定义了类的属性和方法、用代码封装数据处理数据,JS中类是建立在原型上的,所以类中的属性和方法都是通过原型链来实现的。

javascript
class Animal {
  //公有属性(推荐) 
  name
  //定义属性并设置默认值
  age = 18
  //构造函数
  constructor(name) {
    //构造函数内部this,实例化对象
    this.name = name

    //动态添加属性(不建议)
    this.foods = ['apple', 'banana']
  }
  //公有方法
  sayHi() {
    console.log(`My name is ${this.name}`)
  }
}

const a = new Animal('Carlos')

class实现继承

extends 关键字 用于声明类或者类表达式中。创建一个类,该类是另一个类的子类,继承父类的属性和方法。

super 关键字 用于访问对象字面量或类的原型([Prototype])上的属性,用于调用父类的构造函数和父类的方法。

javascript
//父类
class Animal  {
  name = 'Carlos'
  constructor(name) {
    this.name = name
  }
  sayHi() {
    console.log(`My name is ${this.name}`)
  }
}
//----------子类-----------

class Fox extends Animal {
  age = 18
  constructor(name,age) {
    //子类的构造函数必须调用父类的构造函数
    super(name)
    this.age = age
  }
  sayHello(){
    console.log(子类的sayHello)
  }
  //同名方法就近原则,子类同名方法会覆盖父类的同名方法
  sayHi() {
    console.log(`子类的My name is ${this.name},I am ${this.age} years old`)
  }
}
const fox = new Fox('小福泥',20)

静态属性和私有属性

静态属性:静态属性是类本身(而不是类的实例)拥有的属性和方法,它通过 static 关键字声明。静态属性不能被实例化,只能通过类名访问。

私有属性:私有属性是类的实例拥有的属性和方法,它通过 # 符号声明。私有属性只能在类的内部访问,不能在类的外部访问。

ES5 基于原型和构造函数实现继承

ES5 有一下几种继承方法

//出自JavaScript 高级程序设计
1. 原型链继承
2. 借用构造函数继承
3. 组合继承
4. 原型式继承
5. 寄生式继承
6. 寄生组合式继承 (推荐)// 在ES6中,babel编译后是使用这个方法实现继承

原型链 JavaScript 中的对象都有一个内部属性 [[Prototype]],它指向该对象的原型对象。当访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或者到达原型链的末端(即 null)为止。

寄生组合式继承

寄生组合式继承 借用构造函数继承属性,原型链的混成形式继承方法。

Object.create()
javascript
//-------------Object.create()---------------
const food = {
  name : '苹果',
  eat() {
    console.log(`好吃`)
  }

}
//创建一个对象,并继承food
//Objecgt.create()是一个静态方法,用于创建一个新对象.
//可以覆盖原对象的属性和方法
const nFood = Object.create(food,{
  eat:{
    value(){
      console.log(`真好吃`)
    }
  }
})
寄生组合式继承 代码

组合式继承借用构造函数继承属性,原型链继承方法。 寄生:父类的原型有子类的构造函数

javascript
//父类
function Parent(name) {
  this.name = name
}
Parent.prototype.sayHi = function () {
  console.log(`My name is ${this.name}`)
}

//-------组合式继承---------
//通过构造函数实现继承属性
function Son(name) {
  Person.call(this, name)
}
//通过继承链实现继承方法
const prototype = Object.create(Parent.prototype,{
  constructor:{
    value:Son
  }
})
//创建的原型,赋值给子类的原型
Son.prototype = prototype


const son = new Son('Carlos')

网络请求

网络请求是前端开发中常见的需求,常见的网络请求方式有:AJAX、XMLHttpRequest、fetch、axios等。

XMLHttpRequest 是浏览器内置的XMLHttpRequest对象,是一种传统的进行网络请求的方式,目前仍然可用,但因其复杂性和冗长性逐渐被更现代的方法所取代。

AJXA 基于XMLHttpRequest等方式实现无刷新页面更新的一种概念或方法论。

axios 基于Promise,的第三方库;可以在客户端、浏览器、node.js中使用,基于Promise的API来进行HTTP请求,广泛应用于现代Web应用开发中。

fetch 基于Promise,是一种较新的、原生的JavaScript API,用于发起网络请求,以其简洁性和现代化设计受到欢迎。

fetch

fetch() 是一个用于在浏览器中发送网络请求的 JavaScript API,它返回一个 Promise 对象,用于处理异步操作。

在项目开发中更推荐使用axios,它提供了更方便的API和功能,如请求超时、错误处理等。 但在小型项目中更推荐使用fetch(),无需引入axios三方包 。

fetch() 的基本用法

javascript
// 目标 URL
const url = 'https://jsonplaceholder.typicode.com/posts/1';

// 使用 fetch() 发起 GET 请求
fetch(url)
  .then(response => {
    // 检查 HTTP 响应状态码是否为成功(200-299)
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    // 将响应解析为 JSON 格式
    return response.json();
  })
  .then(data => {
    // 处理解析后的数据
    console.log('请求成功,返回的数据:', data);
  })
  .catch(error => {
    // 捕获并处理错误
    console.error('请求失败:', error);
  });

代码说明

目标 URL: https://jsonplaceholder.typicode.com/posts/1 是一个免费的测试 API,返回一条模拟的博客文章数据。

fetch(url): fetch() 方法用于发起网络请求,默认使用 GET 方法。 它返回一个 Promise,该 Promise 在请求完成时会解析为一个 Response 对象。

.then(response => { ... }): response 是服务器返回的响应对象。

response.ok 是一个布尔值,表示请求是否成功(HTTP 状态码在 200-299 范围内)。 如果请求失败(如 404 或 500 错误),我们抛出一个错误以便后续 .catch() 捕获。

response.json(): 将响应体解析为 JSON 格式。 这也是一个异步操作,因此返回的是一个 Promise。

.then(data => { ... }): data 是解析后的 JSON 数据。 在这里可以对数据进行进一步处理,比如更新 DOM、存储到变量中等。

.catch(error => { ... }): 捕获整个请求过程中可能出现的任何错误(例如网络问题、解析错误等)。 输出错误信息以供调试。

fetch 提交FormData

核心语法

1.如何设置请求方法

fetch() 的第一个参数是目标 URL,第二个参数是一个配置对象,其中可以通过 method 属性指定 HTTP 请求方法(如 POST、PUT 等)。

javascript
fetch(url, {
  method: 'POST', // 设置请求方法为 POST
  body: formData, // 请求体为 FormData 对象
});

2.如何提交数据

使用 FormData 对象来构建表单数据。

如果是从 HTML 表单中获取数据,可以将表单元素传递给 FormData 构造函数。

如果是手动构造表单数据,可以使用 formData.append(key, value) 方法。

fetch 提交JSON数据

核心语法

1. 如何设置请求方法 fetch() 的第二个参数是一个配置对象,其中可以通过 method 属性指定 HTTP 请求方法(如 POST、PUT 等)。

javascript
fetch(url, {
  method: 'POST', // 设置请求方法为 POST
});

2. 如何提交 JSON 数据 2-1使用 JSON.stringify() 方法将 JavaScript 对象转换为 JSON 字符串。

2-2在请求头中设置 Content-Type 为 application/json,告诉服务器请求体的格式是 JSON。

2-3将 JSON 字符串作为请求体传递给 body。

fetch 提交JSON数据示例代码
javascript
// 目标 URL
const url = 'https://example.com/api/submit';

// 要提交的 JSON 数据
const jsonData = {
  username: 'Alice',
  email: 'alice@example.com',
  age: 25,
};

// 使用 fetch() 提交 JSON 数据
fetch(url, {
  method: 'POST', // 设置请求方法为 POST
  headers: {
    'Content-Type': 'application/json', // 告诉服务器请求体是 JSON 格式
  },
  body: JSON.stringify(jsonData), // 将 JavaScript 对象序列化为 JSON 字符串
})
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json(); // 解析响应为 JSON
  })
  .then(data => {
    console.log('提交成功:', data);
  })
  .catch(error => {
    console.error('提交失败:', error);
  });

异步编程

在 JavaScript 中,异步编程是指程序可以执行非阻塞操作(如网络请求、文件读取等),而不必等待这些操作完成。JavaScript 使用事件循环来处理异步操作,从而避免了阻塞主线程的问题。以下是三种常见的异步编程模式:Promise、Generator 函数和 async/await。

Promise

Promise 是一个用于处理异步操作的 JavaScript 对象。它代表了一个异步操作的最终结果,并提供了一种统一的接口用于处理异步操作的结果。Promise 有三种状态:

pending 进行中 初始状态,既不是成功也不是失败。

fulfilled 已完成 表示操作成功完成。

rejected 已拒绝 表示操作失败。

javascript
// 创建一个新的 Promise 实例
const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve('操作成功');
        } else {
            reject('操作失败');
        }
    }, 1000);
});

// 使用 then 方法处理成功的响应
myPromise.then(result => {
    console.log(result); // 输出: 操作成功
}).catch(error => {
    console.error(error); // 如果发生错误,则输出错误信息
});

Generator 函数

Generator 生成器函数是 ES6 的一种语法糖,它允许在函数中暂停和恢复执行,并在需要时返回值。Generator 函数使用 yield 关键字来定义暂停点,并在函数调用时返回一个迭代器对象。可以通过 .next() 方法逐步执行函数体中的代码块。

javascript
function* generatorExample() {
    console.log('开始');
    const result1 = yield '第一次暂停';
    console.log(`接收到的第一个值: ${result1}`);
    
    const result2 = yield '第二次暂停';
    console.log(`接收到的第二个值: ${result2}`);
}

const gen = generatorExample();
// 输出: 开始 \n 第一次暂停
console.log(gen.next().value); 
// 输出: 接收到的第一个值: 传递给第一个 yield 的值 \n 第二次暂停
console.log(gen.next('传递给第一个 yield 的值').value); 
// 输出: 接收到的第二个值: 传递给第二个 yield 的值
console.log(gen.next('传递给第二个 yield 的值').value);

async/await

async/await 是基于 Promise 的更高级别的抽象,提供了更加简洁的异步代码编写方式。async 函数总是返回一个 Promise,而 await 关键字只能在 async 函数内部使用,用于等待一个 Promise 完成,并直接获取其结果。

javascript
async function asyncFunction() {
    try {
        // await 等待一个 Promise 完成
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        const data = await response.json();
        console.log(data); // 输出从 API 获取的数据
    } catch (error) {
        console.error('请求失败:', error);
    }
}

// 调用 async 函数
asyncFunction();

Promise 提供了一种处理异步操作的方式,具有三种状态:pending、fulfilled 和 rejected。它是现代 JavaScript 中处理异步操作的基础。

Generator 函数 允许函数内暂停执行,并通过 yield 关键字控制流程。这使得复杂的异步逻辑更容易管理。

async/await 是基于 Promise 的一种更高级别的抽象,简化了异步代码的书写方式,使代码看起来更像是同步代码,提高了可读性和维护性。