前端面试-笔记1
严格模式"use strict"
"use strict"必须在最前位置 全局执行指向全局对象 函数内部取决于被调用方式
- 函数内部直接调用this
- 非严格模式:全局对象(window)
- 严格模式:undefined
- 对象方法调用this
- 指向调用者
示例全局执行
打印全局对象,在浏览器环境下打印window
"use strict"
console.log(this)
this指向
直接调用
非严格模式
function fn(){
console.log(this)
}
fu()
打印输出window
严格模式
"use strict"
function fn(){
console.log(this)
}
fu()
打印输出undefined
对象方法调用
this始终指向调用者
非严格模式
const food = {
name = '苹果',
eat(){
console.log(this)
}
}
打印food对象 { name:'苹果', eat:f }
严格模式
"use strict"
const food = {
name = '苹果',
eat(){
console.log(this)
}
}
打印food对象 { name:'苹果', eat:f }
指定this的值
调用时指定
call方法和apply方法
apply 和 call 是 JavaScript 中用于调用函数的两个方法,它们的主要作用是允许你显式地指定函数内部的 this 值,并传递参数给函数。尽管它们的目的相同,但在参数传递的方式上有所不同。
call 方法: 接受一个参数列表(以逗号分隔的形式直接传递)。
apply 方法: 接受一个数组(或类数组对象)作为参数列表。
call方法
fnc.call(thisArg, 参数, 参数...)
示例
function fnc(num1, num2){
console.log(this)
console.log(num1 , num2)
}
const person = {
name:'张三'
}
fnc.call(person, 1, 2)
打印输出{name:'张三'} 1 2
apply方法
以数组形式传入参数
fnc.call(thisArg, [参数, 参数...])
示例
function fnc(num1, num2){
console.log(this)
console.log(num1 , num2)
}
const person = {
name:'李四'
}
fnc.apply(person, [3,4])
打印输出{name:'李四'} 3 4
创建时指定
bind方法和箭头函数
bind方法
bind 方法在 JavaScript 中用于创建一个新的函数,当这个新函数被调用时,它的 this 关键字会被设置为传给 bind() 的第一个参数,同时还可以预先指定部分或全部参数。这是 JavaScript 函数式编程中非常有用的特性之一,特别是在需要固定函数内部的 this 指向或者部分应用(partial application)函数参数的情况下。
const bindFunc = func.bind(thisArg,参数1 ,参数2...)
示例1
function fnc(num1, num2){
console.log(this)
console.log(num1 , num2)
}
const person = {
name:'张三'
}
const bindFunc = fnc.bind(person, 666)
不会打印出任何东西,只有在调用时才会打印出结果
示例2
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
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不使用箭头函数
const person = {
name:'张三',
myself(){
console.log(this)
setTimeout(function(){
console.log(this)
},1000)
}
}
person.myself()
打印输出{name:'张三'} 一秒后全局对象window
示例2使用箭头函数
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调用原函数、接受剩余参数返回结果。
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属性被覆盖。
thisArg.f = this
解决方法给thisArg加一个一定不重名的属性名保证f一定是不同名称的属性(方法)。
解决方案es6 symbol优化代码
在 JavaScript 中,symbol 是一种原始数据类型(primitive data type),用于创建唯一的标识符。它是 ES6(ECMAScript 2015)引入的一个新特性,主要用于解决对象属性名冲突的题。
Symbol 的一个重要特性:唯一性。即使两个 Symbol 创建时传入了相同的描述字符串,它们仍然是不同的值。下面我将详细解读这段代码,并解释为什么会有这样的行为。
const a = Symbol();
const b = Symbol();
console.log(a === b);
//false
const a = Symbol(); const b = Symbol(); 这里创建了两个 Symbol 值 a 和 b。 即使没有传入任何描述字符串,Symbol() 每次调用都会返回一个全新的、唯一的值。 因此,a 和 b 是两个完全不同的值,即使它们看起来没有任何区别。
console.log(a === b); === 是严格相等运算符,用于比较两个值是否完全相同。 因为 a 和 b 是两个独立的 Symbol 值,所以它们不相等,结果为 false。
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 也是两个完全不同的值。
- console.log(c === d); 同样地,c 和 d 是两个独立的 Symbol 值,因此它们不相等,结果为 false。
为什么 Symbol 总是唯一的?
Symbol 的设计初衷就是为了提供一种独一无二的标识符,以避免命名冲突。以下是关键点:
每次调用 Symbol() 都生成一个新的值: 不管是否传入描述字符串,Symbol() 每次调用都会创建一个全新的值。 描述字符串仅仅是一个可选的标记,用于调试或说明用途,但它不会影响 Symbol 的唯一性。
描述字符串的作用: 描述字符串(如 '111')只是为了方便开发者阅读和调试,它并不参与 Symbol 的值比较。 描述相同的 Symbol 值仍然是不同的,因为它们本质上是独立的标识符。
唯一性保证: Symbol 的唯一性是由 JavaScript 引擎在底层实现的,确保每个 Symbol 值都不会与其他值相等。 这种特性使得 Symbol 非常适合用作对象的属性名,避免与现有属性名发生冲突。
symbol优化代码
/**
* 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方法接收一个数组作为参数。
/**
* 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方法
同上述相同,只是返回一个函数,调用时再执行原函数。
/**
* 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中类是建立在原型上的,所以类中的属性和方法都是通过原型链来实现的。
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])上的属性,用于调用父类的构造函数和父类的方法。
//父类
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()
//-------------Object.create()---------------
const food = {
name : '苹果',
eat() {
console.log(`好吃`)
}
}
//创建一个对象,并继承food
//Objecgt.create()是一个静态方法,用于创建一个新对象.
//可以覆盖原对象的属性和方法
const nFood = Object.create(food,{
eat:{
value(){
console.log(`真好吃`)
}
}
})
寄生组合式继承 代码
组合式继承借用构造函数继承属性,原型链继承方法。 寄生:父类的原型有子类的构造函数
//父类
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() 的基本用法
// 目标 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 等)。
fetch(url, {
method: 'POST', // 设置请求方法为 POST
body: formData, // 请求体为 FormData 对象
});
2.如何提交数据
使用 FormData 对象来构建表单数据。
如果是从 HTML 表单中获取数据,可以将表单元素传递给 FormData 构造函数。
如果是手动构造表单数据,可以使用 formData.append(key, value) 方法。
fetch 提交JSON数据
核心语法
1. 如何设置请求方法 fetch() 的第二个参数是一个配置对象,其中可以通过 method 属性指定 HTTP 请求方法(如 POST、PUT 等)。
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数据示例代码
// 目标 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 已拒绝 表示操作失败。
// 创建一个新的 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() 方法逐步执行函数体中的代码块。
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 完成,并直接获取其结果。
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 的一种更高级别的抽象,简化了异步代码的书写方式,使代码看起来更像是同步代码,提高了可读性和维护性。