5 件可能会让 JavaScript 初学者/OO 开发人员感到惊讶的事情

2025-05-25

5 件可能会让 JavaScript 初学者/OO 开发人员感到惊讶的事情

在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris

TLDR;这不是对 JavaScript 的批评,只是承认它与 OO 语言略有不同,你可以诅咒 JS,也可以利用由此产生的模式为自己谋利。

我喜欢这门语言,但它的工作方式与我习惯的其他语言不同。

无论您是 JavaScript 初学者还是编程新手,JS 中总有一些内容可能会让您感到惊讶。仅仅因为让您感到惊讶并不意味着它是错误的,它只是与众不同、古怪或完全合理,这取决于您之前的经验。接下来讨论的每一个主题几乎都值得写成一篇文章甚至一本书,但这里只列举了:

 -1- 真的真的等于

如果你学过其他语言,比如 Java,你应该知道一个=表示赋值,一个==表示比较。在 JavaScript 中,===和都可以==用来比较相等性。该用哪一个?有什么区别?==还是只比较值?考虑这个例子:

if('2' == 2) {} // true

true当值相同但类型不同时返回。

现在看这个例子:

if('2' === 2) {} // false

if(2 === 2) {} // true

上面的===检测到'2'2具有不同的类型,因此计算结果为false。通常建议使用这种方式进行比较。

-2- 有很多方法可以创建对象

在 Java 或 C# 中,你有一个类。你可以从该类实例化一个对象。这很合理。JavaScript 提供了更多选项。你可以通过以下方式创建对象:

  • 使用类,您可以使用关键字class在类的上下文中定义字段、方法、getter/setter。以下是示例:
class Person {
  constructor(n) {
    this.name = n;
  }

  getName() { return this.name; }
}
  • 对象字面量,你可以定义一个对象而不需要定义类。你只需要{}。它看起来就像这样:
  const person = {
    name: 'chris',
    city: 'location',
    getAll() {
      return `${this.name} ${this.city}`;
    }
  }
  • Object create,你可以使用该方法Object.create()创建一个对象。它需要一个原型对象作为基础。以下是示例:
  const address = {
    city: '',
    country: ''
  } 

  const adr = Object.create(address);
  adr.city = 'London';
  adr.country = 'UK'
  console.log(adr.city); // London
  console.log(adr.country); // UK

块语句,看起来没有范围

块语句、、iffor不会while创建局部作用域。这意味着你在其中创建的任何内容都可以在语句外部访问,如下所示:

for (var i =0; i< 10; i++) {
  console.log(i);
}

console.log(i);

最后console.log()会打印10。这可能会让你感到惊讶。

是啊,JS为什么设计成这样i还活着呢?

问问 Brendan Eich,这是一个功能:)

为了使 JS 的行为像您可能知道的其他语言一样,您需要使用 alet或 a const,如下所示:

for (let i = 0; i< 10; i++) {
  console.log(i);
}

console.log(i);

运行这段代码,现在显示i is not defined。为什么这样有效?因为let允许你声明仅限于块语句作用域的变量。所以,这是由于使用了关键字letover var,而不是块语句被赋予了作用域。(感谢 Will 的评论)

-3- 上下文,this

你可能听过“没人知道什么是全局上下文”这个笑话this。从一个空文件开始this,就是全局上下文。考虑以下代码:

global.name = "cross";

function someFunction() {
  console.log(this.name);
}

someFunction();

上面我们将name变量赋值给global(在 Node.js 中我们这样称呼它,在前端它应该是window)。 的值this来自全局上下文。

让我们看下面一个不同的例子:

var object = {
  name: 'chris',
  getName() {
    console.log(`${this.name}`);
  }
}

object.getName();

这里的值this是对象本身,它知道name是什么,即值chris

改变环境

我们可以改变this现状。JavaScript 中有一些辅助方法可以让我们做到这一点bind()call()apply()。再次考虑这个例子,但object添加了以下内容:

global.name = "cross";

var object = {
  name: 'chris',
  getName() {
    console.log(`${this.name}`);
  }
}

function someFunction() {
  console.log(this.name);
}

someFunction();

我们可以this从全局上下文转换为 的上下文object。下面我们将展示上述任何一种方法如何使用这一原则:

someFunction.bind(object)();
someFunction.call(object)
someFunction.apply(object)

现在它将打印chris,而不是cross

这三种方法的使用方式通常略有不同,但对于这个例子来说,它们是相当等效的。

混乱this

好的,那么我们什么时候会对 的值感到困惑this呢?这种情况不止发生过一次,但一个常见的地方是当我们尝试使用构造函数创建一个对象时,如下所示:

function Person(n) {
  this.name =  n || 'chris';
  function getName() {
    return this.name;
  }
  return {
   getName
  };
}

const person = new Person();
console.log(person.getName()) // undefined 

这是因为this一旦使用内部函数,内部函数就会发生变化new。有不同的解决方案可以解决这个问题:

解决方案 1 - this = that

解决这个问题的一种方法是让它记住outer 的值this。将上面的例子重写如下:

function Person(n) {
  this.name =  n || 'chris';
  var that = this;
  function getName() {
    return that.name;
  }
  return {
   getName
  };
}

const person = new Person();
console.log(person.getName()) // 'chris'

它通过引入记住值的that变量来解决这个问题。但还有其他解决方案。this

解决方案 2 - 箭头函数

function Person() {
  this.name = 'chris';

  const getName = () => {
    return this.name;
  }

  return {
    getName
  }
}

const person = new Person();
console.log(person.getName()) // 'chris'

上述代码替换了function箭头函数的关键字=>

解决方案 3 - 使用闭包

第三种解决方案是使用所谓的closure。这不需要使用new关键字,而是依赖于 JavaScript 几乎不需要使用 的事实this。请考虑以下代码:

function Person() {
  var name = 'chris';

  const getName = () => {
    return name;
  }

  return {
    getName
  }
}

const person = Person();
console.log(person.getName()) // 'chris'

以上内容this已完全删除。我们也不再使用new。在我看来,这是最像 JavaScript 的模式。

解决方案 4 - 将方法放在原型上

在这种方法中,我们使用一个类:

function Person() {
  this.name = 'chris';
}

Person.prototype.getName = function() {
  return this.name;
}

const person = new Person();
console.log(person.getName()) // 'chris'

这是一个很好的解决方案,原因不止一个。它解决了this问题,同时确保该方法只创建一次,而不是每个实例创建一次。

解决方案 5 - 使用类

这与第四种解决方案非常接近:

class Person {
  constructor() {
    this.name = 'chris'
  }

  getName() {
    return this.name;
  }
}

const person = new Person();
console.log(person.getName()) // 'chris'

由于本文篇幅过长,我无法一一列举所有可能与this您想象的不一致的情况。希望这些解决方案能帮助您了解问题发生的时间以及解决方法。

-4-const有效,但可能不是你想象的那样

我们已经看到了关键字是const如何创建局部作用域的。等等,还有更多 :) 这个词const让你觉得它会一直有这个值,它是一个常量,不变等等。好吧……看下面的例子:

const PI = 3.14 // exactly :)
PI = 3;

上面给出了错误Assignment to a constant variable

所以我无法改变它,好

我们来看另一个例子:

const person = {
  name: 'chris'
}

person.name = 'cross'; 

一切正常,没有问题:)

等等,什么?你说值不能改变?

我说过这样的话吗?我没这么说。我说的是这个词const听起来像这样。这const意味着存在一个只读引用,即引用不能被重新赋值。我从来没有说过它不能被改变。请看这个来澄清:

const person = {
  name: "chris",
};

person = {
  name: 'chris'
}

上面给出了一个错误Cannot assign to a constant variable。。

嗯,好的,我可以让它不可变吗?

那么你可以Object.freeze()像这样使用:

Object.freeze(person)

person.name = "cross"; 

console.log(person.name) // 'chris'

太好了,有办法让一切都不可变

嗯。

那又怎样?

它只会在第一层卡住。考虑以下代码:

const person = {
  name: "chris",
  address: {
    town: 'London'
  }
};

Object.freeze(person)

person.name = "cross"; 
person.address.town = 'Stockholm';

console.log(person.address.town) // Stockholm

但是但是..没有办法冻结它吗?

为此,你需要一个深度冻结算法。不过,问问自己,你需要这个吗?我的意思是,在大多数情况下,你的常量通常是原语。

...

const公平地说,这在其他语言中也有点类似。我的意思是,在 C# 中,static readonly如果你想要不可变且锁定的引用,那么在 Java 中,你需要final……

是啊,是啊,无论如何

 -5- 函数调用后还有生命

我们来看下面这段代码:

function aFunction() {
  let name = 'chris';
  console.log(name) // prints chris
}

console.log(name)

没什么特别的,它不知道name最后console.log()一个是什么,因为它在函数之外。我们稍微修改一下:

function aFunction() {
  let name = "chris";
  return {
    getName() {
      return name;
    },
    setName(value) {
      name = value;
    }
  }
}

const anObject = aFunction();
console.log(anObject.getName());
anObject.setName("cross");
console.log(anObject.getName());

此时它打印出chriscalling getName(),好吧,您可能认为它绑定到了某个值。然后您调用setName(),最后再次调用getName(),这次它打印出cross。那么为什么这令人惊讶呢?想想一个函数通常是如何工作的,您调用它,其中的变量就不再存在了。现在再次查看上面的代码,您会注意到,name在函数停止执行很久之后,变量似乎仍然存在。如果您将它与 Objective-C 之类的语言进行比较,这并不奇怪。您已经习惯了引用计数,如果代码的某些部分不再引用某些东西,那么它就会被垃圾回收。显然,您仍然通过anObject变量引用它。

不过,如果你有面向对象编程的背景,你可能会习惯于对象持有状态,并且状态存在于对象本身。在这种情况下,状态name存在于对象外部的词法环境中,这很迷幻吧?;)

最简单的理解方法是用私有变量创建对象。最近我也越来越多地用这种方法创建对象。不过用类也没什么不好,随便你怎么想 :)

概括

我很乐意听听你对其他可能让你惊喜/困惑或让你的生活更美好的事情的评论。因为我对 JavaScript 的很多方面都有同样的感受——我打字少得多。

文章来源:https://dev.to/itnext/5-things-that-might-surprise-a-javascript-beginner-oo-developer-1nje
PREV
我真的需要 SPA 框架吗?
NEXT
正确使用 WebSockets 和 React.js(无需库)