主题
js 的基本数据类型?
- Number:用于表示数字,包括整数和浮点数
- String:用于表示文本或字符序列
- Boolean:用于表示逻辑值
- Null:表示一个空值或“无”的值。只有一个值:
null
- Undefined:当变量被声明了,但没有赋值时,它的值就是
undefined
- Symbol:表示一个唯一的、不可变的数据类型,通常用于对象属性的键
- BigInt:用于表示任意大小的整数。当数值超过
Number.MAX_SAFE_INTEGER
或小于Number.MIN_SAFE_INTEGER
时,可以使用BigInt
什么是变量提升?
变量提升(Variable Hoisting)是代码执行前,js引擎会先处理变量声明
和函数声明
,将它们提升到当前作用域(函数作用域或全局作用域)的顶部
, 并再开始执行代码。这就是所谓的“变量提升”。
然而,需要注意的是,虽然变量声明会被提升,但变量的赋值部分并不会被提升
。因此,如果在声明变量之前就尝试访问该变量的值,结果将是undefined
。
js
console.log(a); // 输出:undefined
var a = 'bar';
需要注意的是,在实际开发中,使用let
和const
声明的变量并不会发生变量提升,它们具有块级作用域,并且存在暂时性死区(Temporal Dead Zone,TDZ)。因此,推荐使用let
和const
来声明变量,以避免由于变量提升可能导致的潜在问题。
js中的原型,原型链分别是什么?
每个对象(object)
都有一个私有属性(__proto__)
指向另一个名为原型(prototype)
的对象。
原型链是 js 中对象继承属性的一个主要机制。在 js 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象自身也可能有原型,这样就形成了一个链条,一直连接到最终为 null
的地方,这个链条就被称为原型链。
js
// JS 代码还没运行的时候,JS 环境里已经有一个 window 对象了
// window 对象有一个 Object 属性,window.Object 是一个函数对象
// window.Object 这个函数对象有一个重要属性是 prototype
// window.Object.prototype 里面有这么几个属性 toString(函数)、valueOf(函数)
const arr = [1, 2, 3];
// 在调用 arr.push 的时候,arr 自身没有 push 属性,就去 arr.__proto__ 上找 push
// 因此 arr.push 实际上是 window.Array.prototype.push
arr.push(1);
// arr 自身没有 valueOf,
// 于是去 arr.__proto__ 上找arr.__proto__ 只有 pop、push 也没有 valueOf
// 于是去 arr.__proto__.__proto__ 上找 arr.__proto__.__proto__ 就是 window.Object.prototype
// 所以 arr.valueOf 其实就是 window.Object.prototype.valueOf
console.log(arr.valueOf())
1..__proto__ === Number.prototype
'1'.__proto__ === String.prototype
true.__proto__ === Boolean.prototype
function Fn() {}
var obj = new Fn()
console.log(obj.__proto__ === Fn.prototype)
// -> true
console.log(obj.__proto__.__proto__ === Object.prototype)
// -> true
console.log(obj.__proto__.__proto__.__proto__ === null)
// -> true
function Person() {}
var person = new Person()
console.log(person.__proto__ === Person.prototype)//true
console.log(Person.prototype.constructor===Person)//true
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
说说 new 操作符具体干了什么?
- 在内存中
创建一个新对象
- 将新对象的
__proto__
被赋值为构造函数的prototype
属性 - 将构造函数中的
this 指向新对象
执行构造函数
中的代码- 如果构造函数返回非空对象,则
返回该对象
;否则返回刚创建的新对象
js
function _new (_constructor, ...args) {
const obj = Object.create(null)
obj.__proto__ = _constructor.prototype
const res = _constructor.call(obj, ...args)
return res instanceof Object ? res : obj
}
说说你对事件循环的理解
事件循环是 js 运行时环境的一个重要组成部分,它主要用于处理异步事件
和回调函数
。
在 js 代码执行时,所有同步任务会立即执行
,而异步任务则会被放入事件队列中等待执行
。当所有同步任务执行完毕后
,事件循环会从事件队列
中取出一个任务,并将其放入调用栈
中执行。当该任务执行完毕后,事件循环会再次从事件队列中取出下一个任务,并重复这个过程。
事件循环的组成主要包括:调用栈
、任务队列
和事件循环线程
。
- 调用栈用来管理代码的执行顺序
- 任务队列用来存放异步任务
- 而事件循环线程则是一个循环,不断地从任务队列中取出任务执行
宏任务:
- 整体的代码script、setTimeout、setInterval、setImmediate、I/O、UI渲染等
微任务:
- await后面的代码, Promise.then、process.nextTick、MutationObserver等。
js
console.log('Start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('End');
// 输出"Start"
// 将 setTimeout 函数的回调函数添加到宏任务队列中
// 将 Promise.then 回调函数添加到微任务队列中
// 输出"End"
// 此时,执行栈为空,事件循环会检查微任务队列,执行微任务中的函数,输出 "Promise"
// 最后,事件循环会执行宏任务队列中的任务,输出"setTimeout"
async function a() {
console.log('a')
await b()
// await 后面的代码相当于 promise.then
console.log('a end')
console.log('c')
}
async function b() {
console.log('b')
}
a()
setTimeout(function () {
console.log('setTimeout')
}, 0)
// new Promise为立即执行函数
new Promise(function (resolve, reject) {
console.log('promise')
resolve()
}).then(function () {
// 微任务
console.log('then')
})
console.log('main end')
// a
// b
// promise
// main end
// a end
// c
// then
// setTimeout
bind、call、apply 有什么区别?
bind、call和apply都是 js 中用于改变函数执行上下文(即this指向)
的方法,但它们之间存在一些重要的区别
执行方式:
- call 和 apply
立即执行
- bind
不会立即执行
,而是返回一个新的函数。这个新的函数在被调用时,其 this 会被设置为 bind 的第一个参数
传参方式:
- call 和 bind 在传递参数时,都是将
参数逐一传入
- apply则使用
数组或类似数组的对象
来传递参数,这意味着你可以使用扩展运算符(...)或数组的 slice 等方法来传递参数
修改this的性质:
- call 和 apply 是
临时修改 this 指向
,即只在调用时改变 this 的指向,调用完成后,this 指向会恢复为原样 - bind 会
返回一个新的函数
,这个新的函数无论何时调用,其 this 都指向 bind 的第一个参数
js
// 不会立即执行,需要手动调用
const LOG = console.log.bind(console);
闭包?
当一个函数内部返回另一个函数时,这个函数可以访问到父级函数的作用域
,这时就形成了一个闭包。
数据封装和私有变量
实现装饰器/函数修饰器
js
function loggingDecorator(fun) {
return function() {
console.log(`Calling function: ${fun.name}`);
const result = fun.apply(this, arguments);
console.log(`Function returned: ${result}`);
return result;
};
}
const f = function() { return 'Hello, world!'; };
const loggedFun = loggingDecorator(f);
console.log(loggedFun());
- 实现记忆化函数:
js
function memoize(fn) {
const cache = new Map();
return function(...args) {
const argsStr = JSON.stringify(args);
cache.has(argsStr) || cache.set(argsStr, fn.apply(this, args));
return cache.get(argsStr);
};
}
function add(a, b) {
console.log('Calculating...');
return a + b;
}
const memoizedAdd = memoize(add);
console.log(memoizedAdd(1, 2)); // Calculating...
console.log(memoizedAdd(1, 2)); // 不会再计算
- 封装循环和定时器
js
function createTimers() {
for (let i = 0; i < 5; i++) {
(function(index) {
setTimeout(function() {
console.log(`Timer ${index} fired!`);
}, index * 1000);
})(i);
}
}
createTimers();
defineProperty和Proxy有什么区别?
defineProperty 是 ES5 中的方法,它可以直接在一个对象上定义一个新属性
,或者修改一个对象的现有属性
,并返回这个对象。vue.js则是采用数据劫持结合发布者 - 订阅者模式的方式,通过 Object.defineProperty()
来劫持
各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
js
var log = console.info.bind(console) , obj = {}
var defineReactive = (obj, key)=> {
Object.defineProperty(obj, key, {
get:(v)=> {
log(`this is get method , value is ${v}`)
},
set:(v)=> {
log(`this is set method , newValue is ${v}`)
}
})
}
defineReactive(obj,'name')
obj.name = 'Chiang'
obj.name
Proxy 可以理解成,在目标对象之前架设一层拦截
,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器
。
js
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
Javascript如何实现继承?
在 js 中,有多种方式可以实现继承,下面列举了几种常见的方法:
- 原型链继承
javascript
Date.prototype.format = function (date, format) {
};
const date = new Date();
date.format();
- 构造函数继承:
js
function Parent() {
this.name = 'parent';
this.colors = ['red', 'blue', 'green'];
}
function Child() {
Parent.call(this); // 继承 Parent
this.type = 'child';
}
var child1 = new Child();
console.log(child1.name); // parent
- ES6 的 class 继承:
javascript
class Parent {
constructor() {
this.name = 'parent';
}
getName() {
return this.name;
}
}
class Child extends Parent {
constructor() {
super(); // 调用父类的 constructor
this.type = 'child';
}
}
let child1 = new Child();
console.log(child1.getName()); // parent
这种继承方式在语法上更接近于传统的面向对象编程,使得代码更易于理解和维护。
谈谈对 this 对象的理解
它代表函数
运行时自动生成的一个内部对象
,只能在函数内部使用
。this 的值取决于函数被调用的方式
,而不是函数被定义的方式。
以下是一些 this
绑定的主要场景:
- 全局上下文:在全局执行上下文中(在浏览器中的全局对象是
window
),this
指向全局对象。
javascript
console.log(this === window); // 输出:true
- 函数直接调用:如果函数是直接调用的,那么
this
绑定到全局对象。在严格模式下('use strict'
),this
是undefined
。
javascript
function fun() {
console.log(this);
}
fun(); // 输出:window 或 undefined(在严格模式下)
- 作为对象的方法调用:当函数作为对象的方法被调用时,
this
绑定到该对象。
javascript
const obj = {
fun: function() {
console.log(this);
}
};
obj.fun(); // 输出:obj 对象
- 构造函数调用:当函数用
new
关键字调用时,它作为构造函数,this
绑定到新创建的对象实例。
javascript
function MyConstructor() {
this.property = 'Hello World';
}
const instance = new MyConstructor();
console.log(instance.property); // 输出:'Hello World'
- 箭头函数:箭头函数不绑定自己的
this
,它会捕获其所在上下文的this
值,作为自己的this
值。
javascript
const obj = {
action: function() {
const fun = () => {
console.log(this);
};
fun(); // 输出:obj 对象
}
};
obj.action();
- 通过
call
、apply
或bind
方法调用:这些方法允许你显式地指定函数调用时的this
值。
javascript
function fun() {
console.log(this);
}
const obj = {};
fun.call(obj); // 输出:obj 对象
typeof 与 instanceof 有什么区别 ?
typeof 是一个一元操作符,它返回的是数据类型
。它可以用于检测原始类型(如 number
、string
、boolean
、undefined
、symbol
、object
)和函数。
然而,需要注意的是,typeof
对于所有的对象(包括数组和 null)都返回 object
。这意味着它不能用来区分数组、null 和其他对象。
js
console.log(typeof 123); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof null); // "object"
console.log(typeof function() {}); // "function"
instanceof 操作符用于测试构造函数的 prototype
属性是否出现在对象的原型链
上。它用来检测一个对象是否是一个已知构造函数的实例。
js
const arr = [];
console.log(arr instanceof Array); // true
const date = new Date();
console.log(date instanceof Date); // true
function MyObject() {}
const obj = new MyObject();
console.log(obj instanceof MyObject); // true
typeof
主要用于检测变量的原始类型。instanceof
主要用于检测对象是否由特定的构造函数创建。
高阶函数
高阶函数是指接收函数作为参数
或者将函数作为输出结果的函数
。在 js 中,高阶函数可以用来实现高阶函数式编程的特性,例如:map、reduce、filter、sort等。
为什么 js 是单线程?
同一个时间只能做一件事
,作为浏览器脚本语言,js的主要用途是与操作DOM以及和用户交互
。这决定了它只能是单线程,否则会带来很复杂的同步问题
。
Object.create 和 new 有什么区别?
js
const Person = function(){};
const p1 = Object.create(Person);
const p2 = new Person();
o = Object.create(null);
// 等价于:
o = { __proto__: null };
Object.create 失去了原来对象的属性的访问
。
DOM 事件模型是什么?
- DOM0:事件模型是早期的事件模型,所有的浏览器都是支持的, 就是直接在
dom对象上注册事件
名称。 - DOM2:事件捕获和事件冒泡。DOM2级中使用
addEventListener
和removeEventListener
来注册
和解除
事件。- preventDefault --
取消事件的默认行为
- stopPropagation --
取消事件进一步捕获或者冒泡
- preventDefault --
什么是事件代理,以及它的应用场景有哪些?
html
<ul id="ul">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
- 减少整个页面所需的内存,提升整体性能
- 动态绑定,减少重复工作
什么是作用域链?
作用域链(Scope Chain)是 js 中用于查找变量和函数的一种机制
。每个 js 函数都会创建一个作用域链。
箭头函数和普通函数有啥区别? 箭头函数能当构造函数吗?
- 箭头函数
不会创建自己的this
,它从自己的作用域链的上一层继承this
- 箭头函数继承而来的 this
指向永远不变
- 箭头函数
不能作为构造函数使用
- 箭头函数
没有arguments
- 箭头函数
没有原型prototype
ajax
js
xhr = new XMLHttpRequest // 创建ajax对象
xhr.open(method,url,async) // 规定请求地址
xhr.onload // 等待服务器相应
xhr.send() // 向服务器发送请求
谈谈 Javascript 中的类型转换机制
常见的类型转换有:
- 显示转换
js
Number()
parseInt()
String()
Boolean()
- 隐式转换
- 比较运算(==、!=、>、<)、if、while需要布尔值地方
- 算术运算(+、-、*、/、%)
你是怎么理解ES6中 Decorator 的?使用场景有哪些?
装饰者模式就是一种在不改变原类, 不使用继承的情况下,动态地扩展对象功能
。
怎么理解ES6中 Generator?使用场景有哪些?
执行 Generator 函数会返回一个遍历器对象
,可以依次遍历 Generator 函数内部的每一个状态
形式上,Generator 函数是一个普通函数,但是有两个特征:
- function 关键字与函数名之间有一个
星号
- 函数体内部使用
yield
表达式,定义不同的内部状态
你是怎么理解 ES6 中 Promise的?使用场景有哪些?
在ES6中,Promise是一种用于处理异步操作的对象
。它代表了异步操作最终的状态
及其结果值
。
Promise具有以下三个状态:
- Pending(进行中)
- Fulfilled(已成功)
- Rejected(已失败)
Promise 的主要应用场景:
- 异步操作:处理异步操作,
避免回调地狱
。 - 多个异步操作的依赖关系:通过
链式调用
的方式,我们可以确保操作的顺序和依赖关系得到正确处理。 - 错误处理:Promise 提供了
catch
方法来捕获异步操作中的错误,并进行相应的处理。这使得错误处理更加方便和统一
,避免了传统回调函数中错误处理的混乱和不易管理的问题。
Promise 方法,如.all()、.allSettled()、.race()、.resolve()、.reject()
Map 和 WeakMap
Map和WeakMap之间的主要区别体现在以下几个方面:
键的类型和限制:
- Map的键可以是任何类型的值,包括基本数据类型(如字符串、数字)和对象类型。
- 而 WeakMap 的键则
必须是对象类型
,因为只有对象才有引用。如果尝试使用基本数据类型作为 WeakMap 的键,会导致 TypeError 异常。
垃圾回收机制的影响:
- 在 Map 中,键和值都会被常规的垃圾回收机制回收。
- WeakMap的键是
弱引用
,这意味着当对象被垃圾回收时,WeakMap 中对应的键值对也会被自动删除。这种特性使得 WeakMap 特别适用于缓存或元数据场景,当对象不再被使用时,WeakMap 可以自动清除对应的数据,避免内存泄漏。
遍历能力:
- Map对象有内置的迭代器,因此可以通过 for…of 循环来遍历键值对。
- WeakMap
没有内置的迭代器
,因此不能直接遍历键值对。
方法差异:
- 虽然 Map 和 WeakMap 都有get、set、has和delete这四个方法用于操作键值对。
- 但Map还有一些其他方法,如keys、values、entries和forEach,用于遍历键值对, WeakMap则没有这些方法。
Set 和 WeakSet
WeakSet和Set在js中都是用于存储唯一值
的集合
类型,但它们之间存在几个关键的区别:
成员类型:
- Set 集合的成员可以是
任意类型的值
,无论是原始类型(如字符串、数字)还是对象类型。Set并不限制其成员的类型。 - WeakSet 则不同,它的成员只能是
对象类型
。它不能包含任何原始类型的值。这意味着,如果你尝试向WeakSet中添加一个非对象类型的值,操作将会失败。
- Set 集合的成员可以是
引用方式:
Set中的对象使用强引用
,即只要对象被 Set 引用,它就不会被垃圾回收机制回收,即使没有其他地方引用这个对象。WeakSet中的对象则是弱引用
。这意味着,如果没有其他地方引用 WeakSet 中的对象,那么这些对象会被垃圾回收机制自动回收。这种弱引用特性使得 WeakSet 特别适用于缓存或存储临时数据的场景,因为它可以自动清理不再需要的对象,避免内存泄漏。
方法和属性:
- Set和 WeakSet 都提供了类似的方法,如 add() 用于添加元素,delete()用于删除元素,has() 用于检查元素是否存在。
WeakSet专注于对象存储和弱引用
,它没有提供size属性
来查询集合的大小,因为计算大小可能需要遍历整个集合,这可能会破坏弱引用的特性。
应用场景:
- Set 支持任意类型的值,并且使用强引用,它适用于需要存储和管理唯一值集合的多种场景,如
列表去重、检查元素是否存在
等。 - WeakSet 则更适合于
存储对象引用
,特别是在不希望因 Set 的强引用而干扰垃圾回收机制的情况下。例如,当你想存储对DOM元素的引用
,但又不想阻止这些元素在不再需要时被垃圾回收时,WeakSet就是一个很好的选择。
- Set 支持任意类型的值,并且使用强引用,它适用于需要存储和管理唯一值集合的多种场景,如
Javscript 数组的常用方法有哪些?
push()
: 在数组末尾添加一个或多个元素,并返回数组的长度。pop()
: 移除并返回数组末尾的元素, 会改变原始数组。splice()
: 从指定位置删除或替换元素,可修改原始数组。reverse()
: 颠倒数组中元素的顺序,会修改原始数组。sort()
: 对数组中的元素进行排序,默认按照字母顺序排序,会修改原始数组。- unshift(): 在数组开头添加一个或多个元素,并返回数组的长度。
- shift(): 移除并返回数组开头的元素,会改变原始数组。
- concat(): 合并两个或更多数组,并返回新的合并后的数组,不会修改原始数组。
- slice(): 从数组中提取指定位置的元素,返回一个新的数组,不会修改原始数组。
- indexOf(): 查找指定元素在数组中的索引,如果不存在则返回-1。
- lastIndexOf(): 从数组末尾开始查找指定元素在数组中的索引,如果不存在则返回-1。
- includes(): 检查数组是否包含指定元素,返回一个布尔值。
- join(): 将数组中的所有元素转为字符串,并使用指定的分隔符连接它们。
- filter(): 创建一个新数组,其中包含符合条件的所有元素。
- map(): 创建一个新数组,其中包含对原始数组中的每个元素进行操作后的结果。
- reduce(): 将数组中的元素进行累积操作,返回一个单一的值。
- forEach(): 对数组中的每个元素执行提供的函数。
阻止事件冒泡和默认事件?
e.stopPropagation:
阻止事件冒泡
- 不让事件向 document 上冒泡,但是
默认事件任然会执行
,当你调用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,
- 不让事件向 document 上冒泡,但是
e.preventDefault:
阻止默认事件
- 比如在 a 标签的绑定事件上调用此方法,链接则不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
说说 Javascript 为什么会存在数字精度丢失的问题,以及如何进行解决?
js 存在数字精度丢失的问题,主要是因为 js 中所有的数字都是以64位浮点数
形式存储的, 是双精度格式
(double precision)。这种格式可以表示非常大或非常小的数,但是并不适合表示精确的小数
。
在二进制浮点数中,有些十进制小数无法精确表示,因此会存在舍入误差
。例如,0.1 在二进制中是一个无限循环小数,因此无法精确表示。当我们在 js 中进行数学运算时,这些误差可能会累积,导致最终结果出现明显的精度问题。
解决 js 数字精度丢失问题的方法有以下几种:
使用整数运算:尽可能避免小数运算,尤其是在需要高精度的场景中。如果必须使用小数,可以尝试将其转换为整数进行计算,然后再转换回小数。例如,可以将货币值以分为单位存储和计算,而不是以元为单位。
使用第三方库:有一些 js 库专门用于处理高精度数字运算,如 decimal.js、big.js 等。这些库提供了高精度的数字类型和运算方法,可以有效避免精度丢失问题。
固定小数位数:如果只需要处理固定小数位数的数字(如货币),可以在运算结束后使用
toFixed()
方法将结果四舍五入到指定的小数位数。但需要注意的是,toFixed()
方法返回的是字符串,而不是数字。避免连续运算:尽量减少连续运算的次数,因为每次运算都可能引入新的舍入误差。可以尝试将多个运算步骤合并为一个步骤,或者使用中间变量存储运算结果。
js
// false
0.1 + 0.1 === 0.2
线程和进程的区别是什么?
做个简单的比喻:进程 = 火车,线程 = 车厢:
一个进程可以包含多个线程
(一辆火车可以有多个车厢)线程在进程下行进
(单纯的车厢无法运行)不同进程间数据很难共享
(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)同一进程下不同线程间数据很易共享
(A车厢换到B车厢很容易)进程要比线程消耗更多的计算机资源
(采用多列火车相比多个车厢更耗资源)进程间不会相互影响,一个线程挂掉可能将导致整个进程挂掉
(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)进程可以拓展到多机,进程最多适合多核
(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存
(比如火车上的洗手间)—— "互斥锁"进程使用的内存地址可以限定使用量
(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)—— “信号量”
内存分配 ?
堆(heap)
- 用于复杂数据类型(
引用类型
)分配空间,例如数组对象、object对象, 它是运行时动态分配内存
的,因此存取速度较慢
。
- 用于复杂数据类型(
栈(stack)
- 存放一些
基本类型
的变量和对象的引用(包含引用类型的值的变量并不是对象本身,而是一个指向该对象的指针,复制该对象的话,实际上是复制该对象的指针),其优势是存取速度比堆要快,并且栈内的数据可以共享,但缺点是存在栈中的数据大小
与生存期
必须是确定
的,缺乏灵活性。
try...catch 可以捕获到异步代码中的错误吗?
js
try {
setTimeout(() => {
throw new Error("err");
}, 200);
} catch (err) {
console.log(err);
}
// setTimeout是一个异步函数,它的回调函数会在指定的延时后被放入事件队列,等待当前执行栈清空后才执行。
// 因此,当setTimeout的回调函数执行并抛出错误时,try...catch已经执行完毕,无法捕捉到异步回调中的错误。
多个 tab 页相互通信,怎么做?
- 使用 LocalStorage
- Broadcast Channel API
- SharedWorker
- PostMessage
- websocket
如何确保你的构造函数只能被 new 调用,而不能被普通调用?
- 借助 instanceof 和 new 绑定的原理,适用于低版本浏览器
js
function Person(firstName, lastName) {
if (!(this instanceof Person)) {
throw new TypeError(
'Function constructor A cannot be invoked without "new"'
);
}
}
- 借助 new.target 属性,可与 class 配合定义抽象类
js
function Person() {
if (!new.target) {
throw new TypeError(
'Function constructor A cannot be invoked without "new"'
);
}
}
- 面向对象编程使用 ES6 Class
立即执行函数的理解
JS立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行
,这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行。
js
(function (test) {
console.log(test);
})(123)
- 不必为函数命名,避免污染全局变量
- 立即执行函数内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
JSONP 是什么?
jsonp是一种数据调用的方式,带 callback
的 json
就是jsonp。下面是一个简单的 JSONP 使用示例:
客户端(js):
javascript
function handleResponse(data) {
console.log(data); // 处理返回的数据
}
var script = document.createElement('script');
script.src = 'http://example.com/api?callback=handleResponse';
document.body.appendChild(script);
在这个例子中,客户端定义了一个handleResponse
函数,用于处理从服务器返回的数据。然后,创建了一个新的<script>
标签,并设置其src
属性为服务器API的URL,同时在URL的查询字符串中包含了callback
参数,其值为客户端定义的回调函数名handleResponse
。最后,将<script>
标签添加到页面的<body>
元素中,这会导致浏览器发起一个GET请求到指定的URL。
服务端(假设使用Node.js和Express):
javascript
const express = require('express');
const app = express();
app.get('/api', (req, res) => {
const callback = req.query.callback;
const data = { name: 'John', age: 30 };
const jsonpData = `${callback}(${JSON.stringify(data)})`;
res.setHeader('Content-Type', 'application/javascript');
res.send(jsonpData);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
在这个服务端示例中,当收到GET请求时,服务器会检查查询字符串中的callback
参数,然后将数据转换为JSON格式,并把它包装在回调函数的调用中。这样,返回给客户端的内容实际上是一段js代码,当浏览器执行这段代码时,就会调用客户端定义的handleResponse
函数,并传入JSON数据作为参数。
完整流程:
- 客户端js代码创建
<script>
标签,并设置其src
属性为服务器的API URL,同时带上回调函数名。 - 浏览器解析并执行
<script>
标签,向服务器发起GET请求。 - 服务器收到请求,解析出回调函数名,将数据转换为JSON格式,并包装在回调函数的调用中,返回给客户端。
- 浏览器接收到服务器返回的js代码,并执行它。
- 执行返回的js代码时,调用客户端定义的回调函数,并传入JSON数据作为参数。
- 客户端的回调函数处理接收到的数据。
需要注意的是,由于 JSONP 依赖于<script>
标签加载,它只支持 GET
请求,且存在安全风险
,如 XSS
攻击。因此,在使用 JSONP 时,需要确保服务器端返回的内容是安全的,并避免在敏感操作中使用它。同时,现代 Web 开发中,更推荐使用 CORS(跨源资源共享)
来处理跨域请求。
fetch ?
Fetch API 提供了一个 js接口,用于访问
和操纵
HTTP 管道的部分,例如请求
和响应
。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。
js
var url = 'https://example.com/profile'
var data = {username: 'example'}
fetch(url, {
method: 'POST', // or 'PUT'
body: JSON.stringify(data), // data can be `string` or {object}!
headers: new Headers({
'Content-Type': 'application/json'
})
}).then(
res => res.json()
)
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response))
说说 sourcemap 的原理?
调试原始源代码会比浏览器下载的转换后的代码更加容易。 source map 是从已转换的代码映射到原始源的文件
,使浏览器能够重构原始源
并在调试器中显示重建的原始源。source map 只有在打开dev tools
的情况下才会开始下载,一般不会影响网页性能。
怎么实现图片懒加载?
给
img
标签加上loading="lazy"
,<img src="./example.jpg" loading="lazy" />
IntersectionObserver
js
const images = document.querySelectorAll("img[data-src]");
const config = {
rootMargin: "0px",
threshold: 0,
};
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target;
let src = img.dataset.src;
if (src) {
img.src = src;
img.removeAttribute("data-src");
}
self.unobserve(entry.target);
}
});
}, config);
images.forEach((it) => {
observer.observe(it);
});
如何判断一个元素是否在可视区域中?
offsetTop、scrollTop
getBoundingClientRect
js
function isInViewPort(element) {
const doc = document;
const docEle = doc.documentElement;
const viewWidth = window.innerWidth || docEle.clientWidth;
const viewHeight = window.innerHeight || docEle.clientHeight;
const { top, right, bottom, left } = element.getBoundingClientRect();
return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;
}
- Intersection Observer
js
const options = {
// 表示重叠面积占被观察者的比例,从 0 - 1 取值,
// 1 表示完全被包含
threshold: 1.0,
root:document.querySelector('#scrollArea') // 必须是目标元素的父级元素
};
const callback = (entries, observer) => { ....}
const observer = new IntersectionObserver(callback, options);
说说 js 中内存泄漏有哪几种情况?
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation)
,也就是说,执行环境会负责管理代码执行过程中使用的内存。垃圾收集器会定期(周期性)找出那些不在继续使用的变量
,然后释放其内存
。
通常情况下有两种实现方式:
标记清除
- 当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“
- 垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉
- 在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了
- 随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存
引用计数
- 语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放
常见内存泄露情况:
意外的全局变量
js
function foo() {
hero = "this is a hidden global variable";
}
function foo() {
this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();
定时器也常会造成内存泄露
闭包
js
function bindEvent() {
var obj = document.createElement('XXX');
var unused = function () {
console.log(obj, '闭包内引用obj obj不会被释放');
};
obj = null; // 解决方法
}
没有清理对DOM元素的引用同样造成内存泄露
js
const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用
深拷贝浅拷贝有什么区别?怎么实现深拷贝?
浅拷贝(即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址)
Object.assign
Array.prototype.slice(), Array.prototype.concat()
使用拓展运算符实现的复制
深拷贝
- jQuery.extend()
- JSON.stringify() [会忽略undefined、symbol和函数]
- 手写循环递归
arguments 这种类数组,如何遍历类数组?
Arguments 对象只定义在函数体中,包括了函数的参数和其他属性。在函数体中,arguments 指代该函数的 Arguments 对象。
js
function foo(name, age, sex) {
console.log(arguments);
console.log("实参的长度为:" + arguments.length);
}
foo('name', 'age', 'sex')
// Arguments对象的length属性,表示实参的长度
// 形参的长度为:3
// 实参的长度为:1
foo(1);
// Arguments 对象的 callee 属性,通过它可以调用函数自身
var data = [];
for (var i = 0; i < 3; i++) {
(data[i] = function () {
console.log(arguments.callee.i)
}).i = i;
}
data[0]();
data[1]();
data[2]();
// 0
// 1
// 2
// 使用 apply 将 foo 的参数传递给 bar
function foo() {
bar.apply(this, arguments);
}
function bar(a, b, c) {
console.log(a, b, c);
}
foo(1, 2, 3)
function func(...arguments) {
console.log(arguments); // [1, 2, 3]
}
func(1, 2, 3);
怎么实现大型文件上传?
分片上传(Chunked Upload)
:将大文件拆分成小的文件块(chunk),然后通过多个并行的请求依次上传这些文件块。服务器接收到每个文件块后进行存储,最后合并所有文件块以还原原始文件。这种方法可以降低单个请求的负载,并允许在网络中断或上传失败时可以从断点续传。流式上传(Streaming Upload)
:客户端使用流方式逐步读取文件的内容,并将数据流通过 POST 请求发送给服务器。服务器端能够逐步接收并处理这些数据流,而无需等待完整的文件上传完成。这种方法适用于较大的文件,减少了内存占用和传输延迟。使用专门的文件上传服务
:有一些第三方服务可供使用,例如云存储服务(如 Amazon S3、Google Cloud Storage)、文件传输协议(如 FTP、SFTP)等。这些服务通常提供了高可靠性、可扩展性和安全性,并且针对大文件上传进行了优化。压缩文件上传
:如果可能,可以在客户端先对文件进行压缩,然后再进行上传。压缩后的文件大小更小,可以减少上传时间和网络带宽消耗。并发上传
:通过多个并行的请求同时上传文件的不同部分,以加快整个上传过程。这需要服务器端支持并发上传并正确处理分片或部分文件的合并。断点续传
:记录上传进度和状态,以便在网络中断或上传失败后能够从上次中断的位置继续上传。可以使用客户端或服务器端的断点续传机制来实现。
js 中如何取消请求?
- 取消 XMLHttpRequest 请求
js
const xhr = new XMLHttpRequest();
xhr.open('GET', '<http://127.0.0.1:3000/api/get>', true);
xhr.send();
setTimeout(() => {
xhr.abort();
}, 1000);
- 取消 Fetch 请求
js
const controller = new AbortController();
void (async function () {
const response = await fetch('<http://127.0.0.1:3000/api/get>', {
signal: controller.signal,
});
const data = await response.json();
})();
setTimeout(() => {
controller.abort();
}, 1000);
- axios
js
const controller = new AbortController();
const API_URL = '<http://127.0.0.1:3000/api/get>';
void (async function () {
const response = await axios.get(API_URL, {
signal: controller.signal,
});
const { data } = response;
})();
setTimeout(() => {
controller.abort();
}, 1000);
map 和 forEach 有什么区别?
- forEach()方法不会返回执行结果,而是undefined。forEach()会修改原来的数组。
- map()方法会得到一个新的数组并返回。
如何让 Proxy 去监听基本数据类型?
Proxy无法直接监听基本数据类型
(如字符串、数字、布尔值等),因为它们是不可变的
。Proxy只能在对象级别上进行操作
,而不是基本数据类型。
当我们尝试使用 Proxy 包装基本数据类型时,会得到一个 TypeError
错误,因为基本数据类型不具有属性和方法
。
js
const value = 'Hello';
const handler = {
set(target, property, value) {
console.log(`Setting property '${property}' to '${value}'`);
target[property] = value;
return true;
}
};
const proxyValue = new Proxy(value, handler); // TypeError: Cannot create proxy with a non-object as target
如果要监听基本数据类型的更改,最好使用其他方式,例如通过触发事件或调用回调函数来通知更改。可以创建一个自定义的数据包装器,将基本数据类型包装在对象中,并在该对象上实现监听逻辑。这样,可以在包装器对象上添加 Proxy 以监听其属性的更改。
js
function ValueWrapper(value) {
this.value = value;
this.onChange = null;
}
ValueWrapper.prototype.setValue = function(newValue) {
this.value = newValue;
if (typeof this.onChange === 'function') {
this.onChange(this.value);
}
};
const wrapper = new ValueWrapper('Hello');
const handler = {
set(target, property, value) {
console.log(`Setting property '${property}' to '${value}'`);
target[property] = value;
if (typeof target.onChange === 'function') {
target.onChange(target.value);
}
return true;
}
};
const proxyWrapper = new Proxy(wrapper, handler);
proxyWrapper.onChange = newValue => {
console.log(`Value changed: ${newValue}`);
};
proxyWrapper.setValue('Hi'); // 输出: Setting property 'value' to 'Hi', Value changed: Hi
addEventListener 第三个参数?
js
addEventListener(type, listener);
addEventListener(type, listener, options);
addEventListener(type, listener, useCapture);
type表示监听事件类型的大小写敏感的字符串
options 可选一个指定有关 listener 属性的
可选参数对象
:- capture 可选一个布尔值,表示 listener 会在该类型的事件
捕获阶段传播到该 EventTarget 时触发
。 - once 可选一个布尔值,表示 listener 在添加之后
最多只调用一次
。如果为 true,listener 会在其被调用之后自动移除。 - passive 可选一个布尔值,设置为 true 时,表示 listener
永远不会调用 preventDefault()
。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看使用 passive 改善滚屏性能以了解更多。 - signal 可选 AbortSignal,该
AbortSignal
的abort()
方法被调用时,监听器会被移除
。
- capture 可选一个布尔值,表示 listener 会在该类型的事件
useCapture 可选一个布尔值,表示在 DOM 树中注册了 listener 的元素,是否要先于它下面的 EventTarget 调用该 listener。当 useCapture(设为 true)时,沿着 DOM 树
向上冒泡
的事件不会触发 listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 DOM Level 3 事件及 js 事件顺序文档。如果没有指定,useCapture 默认为 false。
Promise 的 finally 怎么实现的?
js
Promise.prototype.finally = function(callback) {
const P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
}
Web Worker 是什么?
Web Worker 是 HTML5 标准中提供的一项技术,它可以让 js 脚本在后台线程运行
,从而避免阻塞 UI 线程
。Web Worker 可以创建一个独立的线程来执行脚本
,从而使得主线程
可以专注于用户交互和响应。
Web Worker 的主要特点包括:
- 独立线程:Web Worker 可以在独立的线程中运行 js 代码,从而避免了在主线程中运行耗时任务的风险。
- 沙箱环境:Web Worker 运行的 js 代码在一个受限的沙箱环境中,
不能访问与主线程共享的 DOM、全局变量等资源
,从而保证了数据安全性和代码稳定性。 - 事件通信:Web Worker 可以通过事件来与主线程进行通信,从而实现线程间的数据传递和同步操作。
- 使用 Web Worker 可以改善因大量 JS 计算导致的卡顿问题,增强页面的稳定性和用户体验。
Web Worker 不仅可以在浏览器中运行,还可以在 Node.js 中运行,在实际应用和开发中都有广泛的应用。
说说对 requestIdleCallback 的理解
requestIdleCallback 是一个浏览器 API,它允许我们在浏览器空闲时执行一些任务
,以提高网页的性能和响应速度。
通常情况下,js 代码会占用主线程
,从而阻塞了其他的任务。当页面需要进行一些复杂计算、渲染大量的DOM元素等操作时,就会导致用户的交互体验变得缓慢和卡顿。
requestIdleCallback 的作用就是将一些非关键性的任务从主线程中分离出来
,等到浏览器闲置时再执行
。这样就可以避免占用主线程
,提高页面的响应速度和流畅度。
使用 requestIdleCallback 需要传入一个回调函数,该函数会在浏览器空闲时被调用。回调函数的参数是一个 IdleDeadline
对象,它包含有关浏览器还剩余多少时间可供执行任务的信息。根据该对象的时间戳信息,开发人员可以自行决定是否继续执行任务或推迟执行。
var、let、const之间有什么区别?
- 变量提升
- 暂时性死区 (let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量)
- 块级作用域 (let和const存在块级作用域)
- 重复声明 (let和const在同一作用域不允许重复声明变量)
- 修改声明的变量 (const声明一个只读的常量。一旦声明,常量的值就不能改变)
说说你对轮询的理解
轮询是指在一定的时间间隔内,定时向服务器发送请求
,获取最新数据的过程。轮询通常用于从服务器获取实时更新的数据。
轮询是在固定的时间间隔内向服务器发送请求,即使服务器没有数据更新也会继续发送请求。而长轮询是先发送一个请求,服务器如果没有数据更新,则不会立即返回,而是将请求挂起,直到有数据更新时再返回结果。
轮询会产生大量的无效请求,浪费带宽和服务器资源,并且对服务器的压力比较大。同时,在短时间内频繁地发送请求可能会被服务器视为恶意行为,导致 IP 被封禁等问题。
为了避免轮询的缺点,可以使用 WebSocket、SSE(Server-Sent Events)等技术来实现实时数据更新。
WebSocket 是一种双向通信协议
,能够实现服务器与客户端之间的实时通信
;而 SSE
则是一种基于 HTTP
的单向通信协议
,可以实现服务器向客户端推送实时数据。
null 和 undefined 有什么区别?
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
cookie、localStorage和sessionStorage 三者之间有什么区别
生命周期
- cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效
- localStorage:除非被手动清除,否则将会永久保存。
- sessionStorage: 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。
存放数据大小
- cookie:4KB左右
- localStorage和sessionStorage:可以保存5MB的信息。
http请求
- cookie:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题
- localStorage和sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信
从安全性来说,因为每次 http 请求都会携带 cookie 信息,这样无形中浪费了带宽,所以 cookie 应该尽可能少的使用,另外 cookie 还需要指定作用域,不可以跨域调用(当前页面只能读取页面所在域的 cookie,即 document.cookie ),限制比较多。
localStorage 和 sessionStorage 唯一的差别一个是永久保存在浏览器里面,一个是关闭网页就清除了信息。localStorage 可以用来跨页面传递参数,sessionStorage 用来保存一些临时的数据,防止用户刷新页面之后丢失了一些参数。
js函数有哪几种声明方式?有什么区别?
有 表达式
和 声明式
两种函数声明方式。
js
// 函数提升,所有通过function关键字声明的变量都会被解释器优先编译,不管声明在什么位置都可以调用它,但是它本身并不会被执行。
test(); // 测试
function test() {
console.log("测试");
}
test(); // 测试
// 不会导致函数提升,必须先声明后调用,不然就会报错。
test(); // 报错:TypeError: test is not a function
var test = function() {
console.log("测试");
};
区别:
- 函数声明式变量会声明提前 函数表达式变量不会声明提前
- 函数声明中的函数名是必需的,而函数表达式中的函数名则是可选的。
- 函数表达式可以在定义的时候直接在表达式后面加()执行,而函数声明则不可以。
- 自执行函数即使带有函数名,它里面的函数还是属于函数表达式。
谈谈你对浏览器中进程和线程的理解
它主要包括以下进程:
- Browser 进程:浏览器的主进程,唯一,负责
创建
和销毁
其它进程、网络资源的下载与管理、浏览器界面的展示、前进后退等。 - GPU 进程:用于
3D 绘制
等,最多一个。 - 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建。
- 浏览器渲染进程(浏览器内核):
内部是多线程的
,每打开一个新网页就会创建一个进程,主要用于页面渲染
,脚本执行
,事件处理
等。
浏览器的渲染进程是多线程的,页面的渲染,js 的执行,事件的循环,都在这个进程内进行:
- GUI 渲染线程:负责
渲染浏览器界面
,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。 - js 引擎线程:也称为 js
内核
,负责处理 Javascript 脚本程序、解析 Javascript 脚本、运行代码等。(例如 V8 引擎) - 事件触发线程:用来控制浏览器事件循环,注意这不归 js 引擎线程管,当事件被触发时,该线程会把事件添加到待处理队列的队尾,等待 js 引擎的处理。
- 定时触发器线程:传说中的 setInterval 与 setTimeout 所在线程
- 异步 http 请求线程:在 XMLHttpRequest 连接后通过浏览器新开一个线程请求,将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由 js 引擎执行。
注意,GUI 渲染线程与 js 引擎线程是互斥的,当 js 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 js 引擎空闲时立即被执行。所以如果 js 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
所谓单线程,是指在 js 引擎中负责解释和执行 js 代码的线程唯一,同一时间上只能执行一件任务。
如何顺序执行10个异步任务?
简单的 for 循环是依次进行循环
的,不像 Array.forEach,Array.map 方法是并发执行
的,利用这一特点加 async / await 很容易写出下面这样的代码:
js
(async () => {
const sleep = delay => {
return new Promise((resolve, reject) => {
setTimeout(_ => resolve(), delay)
})
}
const task = (i) => {
return new Promise(async (resolve, reject) => {
await sleep(500)
console.log(`now is ${i}`)
++i
resolve(i)
})
}
let params = -1
for (let i = 0; i < 4; i++) {
params = await task(params)
}
})()
如何让Promise.all在抛出异常后依然有效
在处理多个并发请求时,我们一般会用 Promise.all() 方法。该方法指当所有在可迭代参数中的 promises 已完成,或者第一个传递的 promise(指 reject)失败时,返回 promise。但是当其中任何一个被拒绝的话。Promise.all([..])就会立即被拒绝,并丢弃来自其他所有 promise 的全部结果。也就是说,promise.all 中任何一个 promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用。
js
var p1 = new Promise((resolve, reject) => {
resolve('p1');
});
var p2 = new Promise((resolve, reject) => {
resolve('p2');
});
var p3 = new Promise((resolve, reject) => {
reject('p3');
});
Promise.all([p1, p2, p3].map(p => p.catch(e => '出错后返回的值' )))
.then(values => {
console.log(values);
}).catch(err => {
console.log(err);
})
Promise.allSettled()
方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。
直接在script标签中写 export 为什么会报错?
现代浏览器可以支持用 script 标签引入模块或者脚本,如果要引入模块,必须给 script 标签添加type="module"
。如果引入脚本,则不需要 type。
窗口宽度,高度
- clientWidth/clientHeight 返回的是元素的内部宽度,它的值只包含 content + padding,如果有滚动条,不包含滚动条。
- clientTop 返回的是上边框的宽度。
- clientLeft 返回的左边框的宽度。
- offsetWidth/offsetHeight 返回的是元素的布局宽度,它的值包含 content + padding + border 包含了滚动条。
- offsetTop 返回的是当前元素相对于其 offsetParent 元素的顶部的距离。
- offsetLeft 返回的是当前元素相对于其 offsetParent 元素的左部的距离。
- scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸。
- scrollTop 属性返回的是一个元素的内容垂直滚动的像素数。
- scrollLeft 属性返回的是元素滚动条到元素左边的距离。
什么是点击穿透,怎么解决?
在发生触摸动作约300ms之后,移动端会模拟产生click动作,它底下的具有点击特性的元素也会被触发,这种现象称为点击穿透。
书写规范问题,不要混用 touch
和 click;消费掉 touch 之后的 click。
xml 和 json 有什么区别?
- JSON是js Object Notation;XML是可
扩展标记语言
。 - JSON是基于
js
语言;XML源自SGML
。 - JSON是一种
表示对象
的方式;XML是一种标记语言
,使用标记结构来表示数据项。 - JSON不提供对命名空间的任何支持;XML支持
名称空间
。 - JSON支持
数组
;XML不支持数组。 - XML的文件相对难以阅读和解释;与XML相比,JSON的文件非常
易于阅读
。 - JSON不使用结束标记;XML有开始和
结束标签
。 - JSON的
安全性较低
;XML比JSON更安全。 - JSON
不支持注释
;XML支持注释。 - JSON仅支持
UTF-8
编码;XML支持各种编码。
for...in 和 for...of 有什么区别?
- for...in 循环主要是为了遍历
对象
- for...of 循环可以用来遍历
数组
、类数组对象
,字符串
、Set
、Map
以及Generator 对象
如何使用js计算一个html页面有多少种标签?
js
new Set([...document.querySelectorAll('*')].map(ele=> ele.tagName))
谈谈对 window.requestAnimationFrame 的理解
window.requestAnimationFrame() 告诉浏览器你希望执行一个动画
,并且要求浏览器在下次重绘之前
调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
与 setTimeout 相比,requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机
。requestAnimationFrame 的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次
,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
如何实现上拉加载,下拉刷新?
上拉加载:
js
scrollTop + clientHeight >= scrollHeight;
const doc = document;
const docEle = doc.documentElement;
// 浏览器高度
let clientHeight = docEle.clientHeight;
let scrollHeight = doc.body.scrollHeight;
let scrollTop = docEle.scrollTop;
const distance = 50;
if (scrollTop + clientHeight >= scrollHeight - distance) {
console.log("开始加载数据");
}
下拉刷新:
- 监听原生 touchstart 事件,记录其初始位置的值,e.touches[0].pageY;
- 监听原生 touchmove 事件,记录并计算当前滑动的位置值与初始位置值的差值,大于 0 表示向下拉动,并借助 CSS3 的 translateY 属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
- 监听原生 touchend 事件,若此时元素滑动达到最大值,则触发 callback,同时将 translateY 重设为 0,元素回到初始位置
如何判断页面是通过PC端还是移动端访问?
- navigator.userAgent
js
if (/Mobi|Android|iPhone/i.test(navigator.userAgent)) {
// 当前设备是移动设备
}
// 另一种写法
if (
navigator.userAgent.match(/Mobi/i) ||
navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/iPhone/i)
) {
// 当前设备是移动设备
}
window.screen,window.innerWidth
window.matchMedia()
js
let isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;