【力扣】2619. 数组原型对象的末了一个元素——认识原型与原型链 ...

打印 上一主题 下一主题

主题 866|帖子 866|积分 2598

【力扣】2619. 数组原型对象的末了一个元素——认识原型与原型链


  
题目

请你编写一段代码实现一个数组方法,使任何数组都可以调用 array.last() 方法,这个方法将返回数组末了一个元素。如果数组中没有元素,则返回 -1 。
你可以假设数组是 JSON.parse 的输出效果。
示例 1 :
  1. 输入:nums = [null, {}, 3]
  2. 输出:3
  3. 解释:调用 nums.last() 后返回最后一个元素: 3。
复制代码
示例 2 :
  1. 输入:nums = []
  2. 输出:-1
  3. 解释:因为此数组没有元素,所以应该返回 -1。
复制代码
提示:


  • arr 是一个有效的 JSON 数组
  • 0 <= arr.length <= 1000
解决方案

概述

这个题目引导我们进入 JavaScript 编程的一个有趣部门:向内置原型添加新功能。尽管这由于可能会有潜伏风险,通常不是保举做法,但它确实提供了对 JavaScript 机动和动态特性的深刻理解。在这个挑战中,我们需要向Array原型添加一个last()方法。这个新方法将返回应用到它的任何数组的末了一个元素,如果数组为空则返回 -1。
在 JavaScript 中,数组是对象,所有对象都从它们的原型继续属性和方法。原型是一种用作创建其他对象基础的“模板对象”。在这个上下文中,JavaScript 的 Array 对象是一个全局对象,包罗用于操作数组的方法,这个对象可以通过自定义方法或属性来扩展。
比方,让我们看一下内置的push()方法,它可以将新的项添加到数组的末端并返回新的长度。这个方法是Array原型的一部门,对 JavaScript 中的所有数组都可用:
  1. let arr = [1, 2, 3];
  2. console.log(Array.prototype.hasOwnProperty('push')); // 这将返回 true,因为数组有 push 方法
  3. arr.push(4); // 现在 arr 是 [1, 2, 3, 4]
复制代码
现在,如果你想向所有数组添加一个新的方法,比方 last(),你可以将它添加到 Array 原型中:
  1. Array.prototype.last = function() {
  2.   // 这里放置 last 方法的实现
  3. };
复制代码
你创建的所有数组现在都可以访问这个last()方法:
  1. let arr = [1, 2, 3];
  2. console.log(arr.last()); // 你的实现将决定这将输出什么
复制代码
扩展内置原型,如Array的原型,可能会有潜伏风险,由于如果你的方法名称与未来的 JavaScript 更新或其他库的方法名称辩说,可能会导致意想不到的举动。比方,考虑尝试覆盖Array原型上的push()方法:
  1. Array.prototype.push = function() {
  2.     console.log('push 方法已被覆盖!');
  3. };
  4. let nums = [1, 2, 3];
  5. nums.push(4); // push 方法已被覆盖!
复制代码
在这种情况下,push() 方法不再将元素附加到数组的末端。相反,它仅仅在控制台上记录一条消息。
通常不鼓励覆盖内置方法,push() 方法广泛用于 JavaScript,改变其功能可能导致大量的错误和题目。这在处理第三方库或其他开发者的代码时尤其贫苦,由于他们期望push()方法按预期工作。
如果需要一个内置方法的修改版本,通常发起创建一个单独的方法或函数。比方,你可以开发一个新的函数,将元素附加到数组中,然跋文录一条消息:
  1. function pushAndLog(array, element) {
  2.     array.push(element);
  3.     console.log('元素 ' + element + ' 已添加到数组中。');
  4. }
  5. let nums = [1, 2, 3];
  6. pushAndLog(nums, 4); // 元素 4 已添加到数组中。
  7. console.log(nums); // [1, 2, 3, 4]
复制代码
在这个题目中,你的任务是扩展Array原型,包罗一个last()方法,如果存在,它应该返回数组的末了一个元素,如果数组为空,则返回 -1。
理解这个任务涉及到理解 JavaScript 中的this关键字。在这里,JavaScript 中的this关键字的举动与其他编程语言略有差别。this 的值取决于函数调用时的上下文。在这个题目中,this 将引用当前调用last()方法的对象,它将是一个数组。
在 JavaScript 中,this 的举动与其他编程语言稍有差别。它的值由它的利用上下文决定,这对初学者来说可能会让人感到困惑。因此,了解上下文和this在差别情况下所指的对象是至关告急的。
全局上下文

在全局执行上下文中(即,在任何函数之外),this 无论在严酷模式照旧非严酷模式下,都引用全局对象。
在web浏览器中,全局对象是 window,以是 this 将引用window对象:
  1. console.log(this); // 在浏览器上下文中会记录 "[object Window]"
复制代码
在Node.js环境中,全局对象不是window而是 global。因此,如果在Node.js上下文中运行相同的代码,this 将引用全局对象:
  1. console.log(this); // 在 Node.js 上下文中会记录 "[object global]"
复制代码
函数上下文

在平凡函数内部,this 的值取决于函数的调用方式。如果函数在全局上下文中调用,this 在严酷模式下将为 undefined,在非严酷模式下将引用全局对象。
  1. function func() {
  2.   console.log(this);
  3. }
  4. func(); // 在非严格模式的浏览器上下文中记录 "[object Window]",在严格模式下会记录 "undefined"
复制代码
但是,当函数充当对象的方法时,this 将引用调用该方法的对象。这展示了this的值不绑定于函数本身,而是由函数被调用的方式和位置决定,这个概念称为执行上下文:
  1. let obj = {
  2.   prop: "Hello",
  3.   func: function
  4. () {
  5.     console.log(this.prop);
  6.   }
  7. }
  8. obj.func(); // 记录 "Hello"
复制代码
然而,箭头函数不具有自己的 this。相反,它们从创建时的父作用域继续 this。换句话说,箭头函数内部的 this 值不由它的调用方式决定,而是由它的定义时的外部词法上下文决定:
  1. let obj = {
  2.   prop: "Hello",
  3.   func: () => {
  4.     console.log(this.prop);
  5.   }
  6. }
  7. obj.func(); // 记录 "undefined",因为箭头函数内部的 `this` 不绑定到 `obj`,而是绑定到其外部词法上下文
复制代码
这在某些情况下可能很有效,但它也使得箭头函数不得当需要访问它们被调用的对象的其他属性的方法。
事件处理步伐

在事件处理步伐的上下文中,this 引用附加了事件监听器的元素,与event.currentTarget相同。
  1. button.addEventListener('click', function() {
  2.   console.log(this); // 记录按钮的整个 HTML 内容
  3. });
复制代码
告急的是注意,它不引用常用的event.target属性。让我们澄清event.currentTarget和 event.target `之间的区别。
event.currentTarget:该属性引用附加了事件处理步伐(如 addEventListener)的元素。这是在事件处理步伐函数的上下文中this引用的内容。
event.target:该属性引用引发事件的实际 DOM 元素。对于会冒泡的事件特殊告急。如果你点击内部元素,事件将冒泡到外部元素,触发它们的事件监听器。对于这些外部元素,event.target 将是实际被点击的最内层元素,而 event.currentTarget(或 this)将是当前处理步伐附加到的元素。
  1. <div id="outer">点击我
  2.   <div id="inner">或者点击我</div>
  3. </div>
  4. <script>
  5. document.getElementById('outer').addEventListener('click', function(event) {
  6.   console.log("currentTarget: ", event.currentTarget.id);
  7.   console.log("this: ", this.id);
  8.   console.log("target: ", event.target.id);
  9. });
  10. </script>
复制代码
在这种情况下,如果你点击外部 div,所有三个日志都将打印 "outer",由于点击的元素(target)和处理步伐附加的元素(currentTarget 或 this)是相同的。但是,如果你点击内部 div 中的 “大概点击我” 文本,event.target 将是 "inner"(由于这是你点击的元素),而 event.currentTarget(或 this)仍将是 "outer"(由于这是事件处理步伐附加的元素)。
构造函数上下文

在构造函数内部,this 引用新创建的对象。但是,这里的“新创建”是什么意思呢?要理解这一点,我们需要探讨 JavaScript 中的new关键字。当你在函数调用之前利用 new 时,它告诉 JavaScript 进行四个操作:
创建一个新的空对象。这不是一个函数、数组或 null,只是一个空对象。
使函数内部的this引用这个新对象。新对象与构造函数内的this关联起来。这就是为什么Person(name)内的this.name实际上修改了新对象。
正常执行函数。它像通常情况下执行函数代码一样执行。
如果函数没有返回自己的对象,则返回新对象。如果构造函数返回一个对象,那个对象将被返回,而不是新对象。如果返回其他任何内容,将返回新对象。
new 关键字答应 JavaScript 开发者以面向对象的方式利用语言,从构造函数中创建实例,就像其他语言中的类一样。这也意味着构造函数内部的this关键字将像从基于类的语言中转换的开发者所期望的那样引用对象的新实例。
  1. function Person(name) {
  2.   // 当使用 `new` 调用时,这是一个新的、空的对象
  3.   this.name = name; // `this` 现在有一个 `name` 属性
  4.   // 函数结束后,将返回 `this`,因为没有其他对象被函数返回
  5. }
  6. let john = new Person('John'); // `john` 现在是函数 `Person` 返回的对象,包含一个值为 'John' 的 `name` 属性
  7. console.log(john.name); // 记录 "John"
复制代码
类上下文

在类中,方法内部的this引用类的实例:
  1. class ExampleClass {
  2.   constructor(value) {
  3.     this.value = value;
  4.   }
  5.   logValue() {
  6.     console.log(this.value);
  7.   }
  8. }
  9. const exampleInstance = new ExampleClass('Hello');
  10. exampleInstance.logValue(); // 记录 "Hello"
复制代码
显式 / 隐式绑定

你还可以利用函数上的 .call()、.apply() 或.bind()方法来明白设置 this 的上下文:
  1. function logThis() {
  2.   console
  3. .log(this);
  4. }
  5. const obj1 = { number: 1 };
  6. const obj2 = { number: 2 };
  7. logThis.call(obj1); // 记录 obj1
  8. logThis.call(obj2); // 记录 obj2
  9. const boundLogThis = logThis.bind(obj1);
  10. boundLogThis(); // 记录 obj1
复制代码
绑定方法和永世 this 上下文

JavaScript 提供了一个名为bind的内置方法,答应我们设置方法中的this值。这个方法创建一个新函数,当调用时,将其this关键字设置为提供的值,以及在调用新函数时提供的一系列参数。
bind 方法的独特之处在于它创建了一个永世绑定的this值,无论厥后如何调用该函数,都不会更改this的值。下面的示例演示了bind如何提供一种锁定函数中的this值的方法,在各种情况下都很有资助,比方在设置事件处理步伐时,希望this值始终引用特定对象,大概在利用调用回调函数的库或框架时,希望在回调中控制this引用的对象。
  1. function greet() {
  2.   return `你好,我是 ${this.name}`;
  3. }
  4. let person1 = { name: 'Alice' };
  5. let person2 = { name: 'Bob' };
  6. // 创建一个与 `person1` 绑定的函数
  7. let greetPerson1 = greet.bind(person1);
  8. console.log(greetPerson1()); // 你好,我是 Alice
  9. // 尝试使用 `call` 方法更改上下文;但是,它仍然使用 `person1` 作为 `this` 上下文
  10. console.log(greetPerson1.call(person2)); // 你好,我是 Alice
  11. // 相比之下,正常函数调用允许使用 `call` 方法设置 `this` 上下文
  12. console.log(greet.call(person2)); // 你好,我是 Bob
复制代码
在 JavaScript 中,了解this 关键字的上下文对于操作和与对象交互非常告急,特殊是在处理面向对象编程、事件处理步伐和函数调用的某些方面。了解this的举动有助于改善代码的结构,并使其更可预测和更容易调试。此外,某些设计模式,如工厂模式和装饰器模式,大量利用 this,因此了解其举动对于有效实现这些模式至关告急。
JavaScript 中的一个关键概念是函数对象中的 this 值通常不是固定的 - 它通常是根据函数的执行上下文而确定的,而不是根据其定义的时刻。然而,也有破例情况。利用函数上的 bind()、call() 或apply()方法时,这些方法答应你显式设置函数调用的this值,从而覆盖其默认举动。此外,JavaScript 中的箭头函数举动差别。它们不绑定自己的this值。相反,它们从定义它们的外部词法环境中捕捉this的值,而且这个值在函数的整个生命周期内保持稳定。这些差别使得理解和利用 JavaScript 中的this既具有挑战性又非常告急。
方法 1:扩展数组原型以包罗 .last() 方法

概述

根据题目报告,您需要加强所有数组,使其具有返回数组末了一个元素的方法 .last()。如果数组中没有元素,则应返回-1。
为此,您可以向数组原型添加一个新方法。这个新方法可以通过访问这个this[this.length-1]简单地返回数组的末了一个元素。
添加到数组原型的方法中的this关键字引用调用该方法的数组。
注意:扩展原生原型是 JavaScript 的一个强大功能,但应该谨慎利用。如果其他代码(或更高版本的 JavaScript)添加了同名的方法,则可能会导致辩说。在扩展本机原型时始终保持谨慎。
算法步骤

在名为 last 的数组原型上定义一个新方法。
在这个方法中,查抄数组是否为空。如果是,返回 -1。
如果数组不为空,则返回数组的末了一个元素。末了一个元素可以通过以下方式访问:this[this.length - 1]。
实现

这种方法可以通过各种方式实现。
实现 1:常规 if 查抄

  1. Array.prototype.last = function() {
  2.   if (this.length === 0) {
  3.     return -1;
  4.   }
  5.   return this[this.length - 1];
  6. }
复制代码
实现 2:三元运算符

  1. Array.prototype.last = function() {
  2.   return this.length === 0 ? -1 : this[this.length - 1];
  3. }
复制代码
这个版本利用了一个三元运算符,代码更简便。? 和 : 就像一个简短 if/else。
实现 3:Nullish 合并运算符

  1. Array.prototype.last = function() {
  2.   return this[this.length - 1] ?? -1;
  3. }
复制代码
此版本利用空合并运算符(??)。如果不为null或 undefined,则返回左侧操作数,否则返回右侧操作数。
请注意,此实现假定数组只包罗数字。如果数组的末了一个元素为空或未定义,则此方法将返回-1,这可能会粉饰末了一个元素的实际值。它可能不得当包罗其他数据类型的数组,在这些数组中,null 或 undefined 是有效且差别的值。始终确保利用得当数组中包罗的数据类型的方法。
实现 4:利用数组 pop() 方法

  1. Array.prototype.last = function() {
  2.   let val = this.pop();
  3.   return val !== undefined ? val : -1;
  4. }
复制代码
此版本利用数组pop()方法,该方法从数组中移除末了一个元素并返回它。如果数组为空,则pop()返回 undefined,我们查抄它并将其替换为 -1。需要注意的是,该操作会改变原始数组,这可能并不抱负,详细取决于您的用例。
实现 5:将 Nullish 合并运算符与 Array.prototype.at() 方法结合利用

  1. Array.prototype.last = function() {
  2.   return this.at(-1) ?? -1;
  3. }
复制代码
在此版本中,我们利用ECMAScript 2021中引入的 Array.prototype.at() 方法。此方法接受一个整数值,并返回该索引处的元素,答应利用正整数和负整数。负整数从数组末端开始计数。如果数组为空,则at(-1)将是未定义的,因此我们提供 -1 作为备用。
实现 6:利用 Array.prototype.slice() 方法

  1. Array.prototype.last = function() {
  2.   return this.length ? this.slice(-1)[0] : -1;
  3. }
复制代码
在这种方法中,我们利用Array.prototype.slice()方法。此方法提取数组的一部门并返回新数组。我们通过提供 -1 作为参数来请求末了一个元素。如果数组为空,则slice(-1)[0]将为 undefined,因此我们提供 -1 作为备用。需要注意的是,该方法不会改变原始数组,这与我们前面提到的pop()方法差别。
实现 7:利用默认参数

  1. Array.prototype.last = function() {
  2.   const [lastElement = -1] = this.slice(-1);
  3.   return lastElement;
  4. }
复制代码
此实施利用带缺省值的 ES6 解构。它本质上与slice(-1)[0]版本相同,但具有差别的语法。
实现 8:findLast 方法(适用于 ECMAScript 2022 及之后版本)

此版本利用 Array.prototype.findLast(),这是为 ECMAScript 2022 发起的一种方法,用于查找数组中满足所提供测试函数的末了一个元素。在这里,我们提供了一个始终返回 true 的函数,因此它将返回末了一个元素,如果数组为空,则返回 -1。
请注意,此解决方案可能在某些情况下不起作用,由于findLast()尚未得到广泛支持。请始终查看当前的 JavaScript 文档,了解其可用性和兼容性。如果要在不支持findLast()的环境中利用 findLast(),可以创建 polyfill:
  1. if (!Array.prototype.findLast) {
  2.     Array.prototype.findLast = function(predicate) {
  3.         for (let i = this.length - 1; i >= 0; i--) {
  4.             if (predicate(this[i], i, this)) {
  5.                 return this[i];
  6.             }
  7.         }
  8.         return undefined;
  9.     };
  10. }
复制代码
以下是完备的解决方案,我们还包罗findLast()的 polyfill,根据您的环境可能不需要:
  1. if (!Array.prototype.findLast) {
  2.     Array.prototype.findLast = function(predicate) {
  3.         for (let i = this.length - 1; i >= 0; i--) {
  4.             if (predicate(this[i], i, this)) {
  5.                 return this[i];
  6.             }
  7.         }
  8.         return undefined;
  9.     };
  10. }
  11. Array.prototype.last = function() {  return this.findLast(() => true) ?? -1;}
复制代码
复杂度分析

时间复杂度:O(1)。无论数组的巨细如何,我们只访问数组的末了一个元素,这是一个恒定的时间操作。
空间复杂度:O(1)。这是由于我们没有利用任何随输入数组巨细而扩展的额外空间。在空间复杂性分析中不考虑数组本身,由于它是函数的输入。我们只考虑该函数利用的任何额外空间。
需要注意的是,就时间和空间复杂性而言,将方法添加到阵列原型不会影响其他阵列,由于它不会为每个阵列重复该方法。相反,该方法驻留在原型中,而且可以由所有数组访问。这使得它成为一种高度节流空间的操作。
方法 2:利用 ES6 Getters

概述

在 JavaScript 中,getter 是获取特定属性的值的方法。在这里,我们将为末了一个属性创建一个 getter。
算法


  • 通过为末了一个属性定义一个 getter来加强数组原型。
  • getter 函数将返回另一个函数,该函数返回数组的末了一个元素,如果数组为空,则返回 -1。
实现

  1. Object.defineProperty(Array.prototype, 'last', {
  2.   get: function() {
  3.     return () => this.length ? this[this.length - 1] : -1;
  4.   }
  5. });
复制代码
当你定义一个getter时,你实际上是把 last 当作一个属性而不是一个函数。因此,它是通过array.last而不是array.last()访问的。如果您将数组的末了一个元素视为该数组的属性,而不是函数的效果,则这种观点在语义上会更清晰。Getter可以提供一种更精炼的、类似于属性的语法,以加强可读性,特殊是当您要实现的操作不需要任何参数而且在概念上是一个属性时。
此外,当在大量利用getter和setter的代码库中工作时,利用getter可以提高一致性。然而,在您的特定题目的上下文中注意到这一点很告急:由于getter被视为一个属性,以是需要一个嵌套的函数来通过在线判断。这个附加层提供了一种与属性交互的方法,使评测性可以或许实现预期的适当功能。
复杂度分析

时间复杂度:O(1)。在 JavaScript 中,访问数组中特定索引处的元素是一个恒定的时间操作。
空间复杂度:O(1)。不会利用额外的空间。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表