乐闻世界logo
搜索文章和话题

JavaScript面试题手册

什么是 js bridge?jsb 有哪些实现方式?

什么是 JS Bridge?JS Bridge,即JavaScript Bridge,是一种在不同环境或者平台之间进行通信的技术,尤其是在Web视图(如WebView)中嵌入的JavaScript与宿主应用(通常是移动应用)之间的通信桥梁。通过JS Bridge,可以使得原生应用可以调用JavaScript代码,JavaScript代码也能调用原生应用的API,从而实现混合开发,充分利用原生应用的性能优势与Web技术的灵活性。JSB 有哪些实现方式?实现JS Bridge的方式通常取决于所使用的平台和具体的需求,以下是一些常见的实现方式:URL Scheme:这是一种较为简单的实现方式。原生应用可以通过拦截WebView中的URL跳转请求来实现与JavaScript的通信。当JavaScript需要与原生应用通信时,它会尝试跳转到一个特殊的URL(如 myscheme://someaction?param1=value1),原生应用拦截这个URL请求,并解析出相应的行为和参数,然后执行相应的原生代码。注入API对象:通过原生代码将一些对象或者方法注入到WebView中的JavaScript环境里。Injected API可以直接在JavaScript代码中被调用,从而实现JavaScript对原生功能的调用。这种方式可以提供更加丰富和直接的接口给JavaScript。消息通道(如postMessage):现代的Web视图组件(如iOS中的WKWebView)支持使用类似于HTML5中定义的 postMessage API的消息通道,可以实现双向通信。JavaScript可以通过这个API发送消息给原生代码,并且原生代码也可以返回消息给JavaScript。Native Modules:在一些JavaScript框架中,如React Native,可以创建特定的Native Modules来扩展原生应用的功能。这些模块可以被JavaScript代码直接调用,原生代码也可以向JavaScript环境发送事件。示例:假设我们需要实现一个简单的功能,让WebView中的JavaScript能够打开原生应用的摄像头并拍照。使用URL Scheme的方式实现可能如下:在JavaScript中,你可能会有这样的调用代码:function openNativeCamera() { window.location.href = 'myapp://camera/open';}在原生应用中(以iOS为例),你可能需要实现如下的拦截逻辑:- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSURL *URL = [request URL]; if ([[URL scheme] isEqualToString:@"myapp"]) { if ([[URL host] isEqualToString:@"camera"] && [[URL path] isEqualToString:@"/open"]) { // 调用原生摄像头的代码 [self openCamera]; return NO; } } return YES;}- (void)openCamera { // 打开摄像头的逻辑}以上示例展示了如何通过URL Scheme拦截WebView中的跳转来调用原生功能。其他实现方式会有不同的代码示例,但核心思想都是建立一个通信通道,让JavaScript与原生代码能够互相调用。
阅读 70·2024年6月24日 16:43

AMD 和 Commonjs 的区别是什么?

AMD(Asynchronous Module Definition)和CommonJS是两种流行的JavaScript模块化规范,它们都被设计来允许代码分割成可重用的模块。但是,它们在模块定义、加载机制和使用场景上有所不同。AMD(Asynchronous Module Definition)主要特点:异步加载: AMD被设计用于浏览器环境,可以异步加载模块,不会阻塞页面渲染。定义模块: 使用define函数定义模块,可以显式声明依赖,并在所有依赖加载完成后运行模块的工厂函数。依赖前置: 依赖需要在模块开始加载之前声明,这样可以并行加载依赖。示例:define(['dependency1', 'dependency2'], function (dep1, dep2) { // 定义模块内容 var moduleName = function () { // 一些功能 }; return moduleName;});实现库: RequireJS 是最著名的实现AMD规范的库。CommonJS主要特点:同步加载: CommonJS被设计用于服务器环境,如Node.js,通常假设模块是在本地文件系统中,因此可以同步加载,不会造成浏览器卡顿。定义模块: 使用exports对象或module.exports定义模块的对外接口。后置依赖: 依赖关系通常是在模块的代码体内通过require调用来声明的。示例:var dep1 = require('dependency1');var dep2 = require('dependency2');function moduleName() { // 一些功能}module.exports = moduleName;实现环境: Node.js 原生支持CommonJS模块规范。总结区别加载方式:AMD为异步加载,适合浏览器环境;CommonJS为同步加载,适合服务器环境如Node.js。定义模块:AMD使用define方法,CommonJS使用exports或module.exports。依赖声明:AMD的依赖声明是前置的,而CommonJS依赖通常在需要时才声明。实现和适用环境:AMD的典型实现是RequireJS,主要用在浏览器端;CommonJS的实现是Node.js的模块系统,主要用在服务器端。在实际应用中,如果是开发面向浏览器的应用,并且需要异步加载模块,可能会倾向于使用AMD规范。如果是开发Node.js服务器端应用,CommonJS会是一种更加自然的选择。现代开发中,也有越来越多的工具和语法(如ES6模块)被用来跨越这两种规范的限制,实现更统一的模块化开发体验。
阅读 20·2024年6月24日 16:43

什么是defineProperty方法?什么时候需要用到defineProperty?

Object.defineProperty 方法是 JavaScript 中的一个非常重要的方法,它允许精确地添加或修改对象的属性。通过这个方法,我们可以控制属性的各种特性,如它们是否可写、可枚举或可配置,甚至定义存取器属性(getter 和 setter)。Object.defineProperty 方法接受三个参数:要在其上定义属性的对象。将被定义或修改的属性的名称。描述符对象,它定义了属性的具体行为和特征。属性描述符对象可以包含以下键:value:属性的值。writable:如果为 true,则属性的值可以被重写。configurable:如果为 true,则该属性的描述符可以被改变,也可以从对象上删除该属性。enumerable:如果为 true,则该属性会出现在对象的枚举属性中。get:作为属性的 getter 函数,当访问属性时触发。set:作为属性的 setter 函数,当属性值被修改时触发。以下是一个使用 Object.defineProperty 的例子:const person = {};Object.defineProperty(person, 'name', { value: '张三', writable: false, enumerable: true, configurable: true});console.log(person.name); // 输出 '张三'person.name = '李四'; // 由于 writable 是 false,这里的赋值操作不会成功console.log(person.name); // 仍然输出 '张三'在上面的例子中,我们定义了一个对象 person 并使用 Object.defineProperty 给它添加了一个名为 name 的属性。这个属性被设置为不可写,所以尝试更改它的值将不会有任何效果。需要用到 Object.defineProperty 的情况包括:控制属性特性:当你需要精细地控制一个对象属性的可枚举性、可配置性、可写性等。创建只读属性:当你希望对象的某些属性是只读的,不允许修改。定义存取器属性:如果你想通过 getter 和 setter 控制属性的访问和赋值。保护对象结构:在对象创建后保护其结构不被意外改变,比如防止属性被删除。这些特性使得 Object.defineProperty 成为在创建具有特定行为的对象属性时非常有用的工具,特别是在模块和库的开发中,我们经常需要确保对象的接口行为符合预期,防止意外的修改。
阅读 16·2024年6月24日 16:43

javascript 定时器为什么是不精确的?

JavaScript 中的定时器包括 setTimeout 和 setInterval,这两个函数通常用于延迟执行或定期执行某些代码。但是,它们的执行时间并不精确,原因主要有以下几点:单线程和事件循环:JavaScript 是单线程执行的,它依赖于事件循环来处理异步事件。当你设置一个定时器时,你实际上是告诉事件循环在未来的某个时间点上执行一段代码。但是,如果事件循环在处理其他任务时被阻塞,例如执行一个耗时的同步操作,那么定时器的代码执行将会被推迟直到主线程空闲为止。Web 浏览器的定时器分辨率:出于性能和节能的考虑,Web 浏览器的定时器分辨率不会非常高。这意味着定时器的回调函数可能不会在预期的精确毫秒数后立即执行。不同的浏览器和不同的环境(比如浏览器后台运行或系统睡眠)可能会对定时器的精度有不同的影响。最小延迟时间:在 HTML5 标准中,规定了 setTimeout 和 setInterval 的最小延迟时间通常不得低于4ms(若之前调用了5次以上的定时器,则最小延迟10ms)。这意味着即使你请求更短的时间间隔,执行也会被推迟到这个最小值。浏览器标签页的背景化:当用户将包含JavaScript定时器的浏览器标签页置于后台时,大多数现代浏览器会减少定时器的执行频率以节约资源和电池寿命。这可能导致后台标签页中的定时器变得非常不精确。垃圾回收:JavaScript的垃圾回收机制可能会暂时阻塞主线程,从而延迟定时器的执行。例如,假设你有一个使用 setTimeout 指定在10ms后执行的回调函数,但是此时主线程正在处理一个复杂的计算任务,这可能需要100ms才能完成。在这种情况下,即使定时器的时间到了,回调函数也必须等到计算任务完成后,事件循环才能调用它。所以实际上回调函数的执行时间将晚于预期的10ms。综上所述,由于单线程的特性、事件循环的机制、Web浏览器的实现细节和环境限制,JavaScript定时器的执行时间就不能保证绝对精确。开发者在使用定时器时,应该考虑到这些因素,并且不应该依赖定时器来进行精确的时间控制。
阅读 24·2024年6月24日 16:43

当添加原生事件不移除时,为什么会出现内存泄露?

内存泄露通常是指在程序运行过程中,分配的内存未能及时释放,导致随着时间的推移,系统可用内存逐渐减少。当我们在JavaScript中处理原生DOM事件时,如果不正确移除这些事件监听器,很容易导致内存泄露。在详细解释原因之前,先来了解一下事件监听器和内存泄露的基本概念。事件监听器是我们绑定到DOM元素上的函数,用来响应特定的事件,比如点击或者键盘按键。而内存泄露则是指已经不再需要的内存,由于各种原因,没有被及时回收,使得应用程序占用的内存越来越多。现在,让我说明为什么不移除原生事件监听器会导致内存泄露:事件监听器与DOM元素的引用关系:当你给一个DOM元素添加事件监听器时,浏览器会创建一个引用,指向该监听器的函数。即使你从DOM树中移除了这个元素,如果事件监听器没有被移除,那么浏览器的事件处理系统仍然会保留一个对该函数的引用,这导致了DOM元素和监听器函数无法被垃圾回收机制回收,因为从技术上讲,它们依旧是可达的。闭包:在JavaScript中,闭包是一个常见的功能,它允许函数访问并操纵函数外部的变量。如果事件监听器是一个闭包,它可能会引用其他外部变量或对象。只要这个监听器存在,所有它引用的对象也都无法被回收,即使这些对象已经没有其他用途。复杂的引用链:在大型的web应用程序中,DOM元素、事件监听器、以及其他相关对象可能会构成复杂的引用链。这些引用链使得垃圾回收变得复杂,如果链中的某一部分没有被正确管理和解除引用,就可能导致整个链条上的对象都不能被回收。让我举一个例子来说明这个问题:假设我们有一个简单的网页应用,它有一个按钮,当用户点击时会弹出一个警告框:function setup() { var button = document.getElementById('myButton'); button.addEventListener('click', function handleClick() { alert('Button clicked!'); });}setup();如果我们某个时刻决定移除这个按钮:var button = document.getElementById('myButton');button.remove();虽然DOM元素被移除了,但是我们没有移除与之绑定的handleClick事件监听器。这意味着handleClick依然保留着对button元素的引用,因此即使我们从DOM中移除了这个元素,它也不会被垃圾回收。如果我们的应用中有很多这样的操作,随着时间的推移,内存消耗将不断增加,这就是内存泄露。解决这个问题的一个方法是在移除DOM元素之前,显式地移除事件监听器:var button = document.getElementById('myButton');button.removeEventListener('click', handleClick);button.remove();这样,我们就手动断开了事件监听器与DOM元素之间的引用关系,使得这些对象可以被垃圾回收。这是为什么在处理原生DOM事件时,开发者需要注意正确地添加和移除事件监听器,以避免不必要的内存
阅读 17·2024年6月24日 16:43

localstorage 对象有哪些 API?

LocalStorage 是 Web Storage API 的一部分,它允许网页在用户浏览器本地存储数据。下面是 LocalStorage 对象常用的 API 方法:setItem(key, value): 该方法用于在 LocalStorage 中存储数据。它接收两个参数,key(键)表示存储数据的名称,而 value(值)则是实际存储的数据。例如:localStorage.setItem('username', 'JohnDoe');getItem(key): 通过 key(键)检索存储在 LocalStorage 中的值。如果指定的键不存在,它将返回 null。例如:let userName = localStorage.getItem('username');console.log(userName); // 输出: JohnDoeremoveItem(key): 删除 LocalStorage 中的指定 key(键)的数据。例如:localStorage.removeItem('username');clear(): 清除 LocalStorage 中的所有数据。例如:localStorage.clear();length: 一个只读属性,返回 LocalStorage 中的数据项数。例如:let numberOfItems = localStorage.length;console.log(numberOfItems);key(index): 根据索引来获取 LocalStorage 中的键。索引从 0 开始。如果索引超出了范围,将返回 null。例如:let firstKeyName = localStorage.key(0);console.log(firstKeyName);LocalStorage 的数据会以字符串的形式存储,即使你存储的是数字或是对象。如果需要存储对象,通常会使用 JSON.stringify 方法将对象转换为字符串格式存储,检索时再用 JSON.parse 方法将字符串转换回对象。例如:let user = { name: 'John', age: 30 };localStorage.setItem('user', JSON.stringify(user));let retrievedUser = JSON.parse(localStorage.getItem('user'));console.log(retrievedUser); // 输出: { name: 'John', age: 30 }以上就是 LocalStorage 对象的主要 API。需要注意的是,虽然 LocalStorage 提供了方便的本地存储机制,但它并不适合存储敏感信息,因为这些信息是以明文形式存储的,且没有到期时间,容易受到跨站脚本(XSS)攻击的影响。
阅读 55·2024年6月24日 16:43

详细说明闭包原理以及应用场景

闭包是一个函数以及创建该函数的词法环境的组合。闭包使得一个函数可以访问到它被定义时的作用域中的变量,即使该函数在其定义环境外被执行。这个概念在JavaScript等支持一等函数的编程语言中尤为重要。闭包的原理当你在JavaScript中创建一个函数时,该函数会记住它被创建时候的环境。在函数中定义的变量,以及它的父作用域中的变量,都会被闭包保留。在函数执行时,如果它访问了这些外部变量,即便父作用域已经执行完毕,这些变量依然可以被访问,因为闭包中保留了它们的引用。应用场景闭包在JavaScript编程中非常有用,它们有许多的应用场景:数据封装闭包可以用于创建私有变量,这样你就可以封装数据,只暴露必要的操作接口。function createCounter() { let count = 0; return { increment() { count++; }, get() { return count; }, };}const counter = createCounter();counter.increment();console.log(counter.get()); // 输出 1在上面的例子中,count 是一个私有变量,通过闭包的形式封装在 createCounter 函数中。外部代码无法直接访问 count 变量,只能通过 increment 和 get 方法间接操作它。回调函数与异步操作异步操作,如定时器、网络请求或事件处理中,闭包常用于保持对某个变量的引用。function delayedGreeting(name) { setTimeout(function() { console.log('Hello, ' + name); }, 1000);}delayedGreeting('Alice');在这个例子里,即使 delayedGreeting 函数的执行已经结束,传给 setTimeout 的匿名函数依然能够访问 name 变量。循环中创建闭包循环中创建函数时,闭包可以帮助每个函数记住它们各自的环境。for (var i = 0; i < 3; i++) { (function(index) { setTimeout(function() { console.log('Value is ' + index); }, 1000); })(i);}在这个例子中,立即执行的函数表达式(IIFE)创建了一个新的词法作用域,这样每次迭代都会保存各自的索引值 index。函数柯里化闭包可以用来实现函数柯里化,即创建已经设置了一些固定参数的新函数。function multiply(a, b) { return a * b;}function curriedMultiply(a) { return function(b) { return multiply(a, b); };}const double = curriedMultiply(2);console.log(double(5)); // 输出 10在这个例子中,curriedMultiply 函数通过闭包记住了参数 a,并返回了一个新函数,这个新函数接受参数 b 并调用原本的 multiply 函数。结论闭包是函数编程中的一个强大特性,它不仅允许你访问定义函数时的作用域,而且能够在数据隐藏、封装和柯里化中发挥重要作用。了解和利用闭包,可以让你编写出更加灵活和强大的代码。
阅读 25·2024年6月24日 16:43

如何实现 javascript 的 bind 方法

JavaScript 中的 bind 方法用于创建一个新函数,该函数在被调用时会将其 this 关键字设置为提供的值,同时还可以接受一系列的参数。要实现一个自定义的 bind 函数,我们需要了解几个关键点:返回一个函数。确定 this 的值。参数的传递。下面是一个简单的实现例子:Function.prototype.myBind = function(context, ...args) { // this 指向调用 myBind 的函数 var fn = this; // 返回一个新的函数 return function(...newArgs) { // 使用 apply 方法调用函数,设定 this 的值,并传递参数 // 这里将预置的参数和新传入的参数拼接在一起 return fn.apply(context, args.concat(newArgs)); };};在这个例子中,myBind 函数接受了两个参数:context 指定了 this 的上下文,args 是一个由预置参数组成的数组。返回的函数在被调用时,会通过 apply 方法将 this 绑定到 context 对象,并将预置参数与新参数合并传递给原函数。让我们通过一个具体的例子来演示这个 myBind 方法的使用:function greet(greeting, punctuation) { console.log(greeting + ', ' + this.name + punctuation);}var person = { name: 'John'};// 使用原生的 bind 方法var greetJohn = greet.bind(person, 'Hello');greetJohn('!'); // 输出: Hello, John!// 使用我们自定义的 myBind 方法var greetJohnCustom = greet.myBind(person, 'Hi');greetJohnCustom('?'); // 输出: Hi, John?这里,我们定义了一个 greet 函数,它接受两个参数 greeting 和 punctuation,然后打印出问候语。我们使用 bind 方法(和我们的 myBind 方法)创建了一个新的函数 greetJohn(和 greetJohnCustom),它的 this 被绑定到 person 对象上,并且预置了“Hello”(和 "Hi")作为 greeting 参数。通过上面的例子,我们演示了如何实现和使用一个自定义的 bind 函数,它模仿了原生 bind 方法的行为。
阅读 19·2024年6月24日 16:43

JS内存泄露如何检测?场景有哪些?

JavaScript内存泄露是指在应用程序中不再需要使用的内存由于某些原因没有被释放或回收,导致可用内存逐渐减少,最终可能会导致应用程序或系统性能下降,甚至崩溃。 如何检测JS内存泄露?检测JavaScript内存泄露通常可以通过以下途径进行:浏览器开发者工具:大多数现代浏览器都提供了内置的开发者工具,可以用来监视内存使用情况。例如,Google Chrome的开发者工具中有"Performance"和"Memory"面板,允许开发者记录和分析网站的运行时性能和内存使用情况。堆快照(Heap Snapshots):通过浏览器的开发者工具,可以捕获堆快照,它会展示内存分配的静态视图。通过比较连续的堆快照,可以观察到哪些对象被分配内存后没有被释放。时间线记录(Timeline Profiling):这个工具可以帮助我们理解内存是如何随着时间的推移而增加的。我们可以使用浏览器工具的时间线功能记录一个时间段内的内存使用情况,寻找内存使用上升的趋势。代码审查(Code Review):定期进行代码审查以查找常见的内存泄露模式,如未取消的事件监听器、闭包的滥用、未清除的定时器等。内存泄露场景内存泄露可能出现在多种不同的场景中,以下是一些常见的场景:全局变量:意外地创建全局变量会导致这些变量不被回收,例如,忘记使用 var、let或 const关键字。事件监听器未移除:如果在DOM元素上添加了事件监听器,但在不需要时没有正确移除,它们会持续占用内存。闭包:不当使用闭包可能会导致父作用域中的变量无法被释放。DOM引用:JavaScript中的变量如果引用了已经从DOM中移除的元素,如果引用一直保持,那么这部分内存也不会被回收。定时器:设置了定时器(如 setInterval)而没有清除(clearInterval),可能会导致内部回调函数和相关变量长期占用内存。第三方库:使用的第三方库如果存在内存泄露,同样也会影响到使用它的应用程序。举个具体的例子:在开发一个单页应用时,我注意到随着页面的使用时间增加,页面的响应速度逐渐变慢。我使用Chrome开发者工具中的Performance面板进行了记录,发现内存使用量呈现持续上升的趋势。通过分析和比较不同时间点的堆快照,我发现存在一个大量DOM元素对应的监听器没有在元素被移除时一并清理。修复这个问题后,应用的性能得到了显著的提升。
阅读 22·2024年6月24日 16:43

如何实现Promise的resolve?

在JavaScript中,Promise 对象是异步编程的一种解决方案。一个 Promise 在创建时处于 pending(等待)状态,可以通过其 resolve 方法转变为 fulfilled(成功)状态,或通过其 reject 方法转变为 rejected(失败)状态。要实现 Promise 的 resolve,通常是在异步操作成功完成时调用。下面是一个简单的例子说明如何使用 Promise 的 resolve 方法:function asyncOperation() { // 创建一个新的Promise对象 return new Promise((resolve, reject) => { // 执行异步操作 setTimeout(() => { const operationWasSuccessful = true; // 假设这是基于异步操作结果的条件 if (operationWasSuccessful) { resolve('Operation successful'); // 如果操作成功,调用resolve并传递结果 } else { reject('Operation failed'); // 如果操作失败,调用reject并传递错误信息 } }, 1000); // 假设这个异步操作需要1秒钟 });}asyncOperation() .then(result => { console.log(result); // 打印成功结果 }) .catch(error => { console.error(error); // 打印错误信息 });在上述代码中,asyncOperation 函数返回一个新的 Promise 对象。在这个 Promise 的构造函数中,有两个参数:resolve 和 reject。这两个参数也是函数,它们被用来分别处理异步操作的成功和失败情况。在异步操作(这里使用 setTimeout 模拟)完成后,根据操作的结果调用 resolve 或 reject。如果异步操作成功(在这个例子中,我们假设 operationWasSuccessful 为 true),则调用 resolve 函数并传递结果消息 'Operation successful'。这将使得 Promise 对象的状态变为 fulfilled,并将结果传递给随后的 .then 方法的回调函数。如果异步操作失败,就调用 reject 函数并传递错误消息 'Operation failed'。这将使得 Promise 对象状态变为 rejected,并将错误信息传递给随后的 .catch 方法的回调函数。
阅读 33·2024年6月24日 16:43