六个Javascript新手需要注意的事情
我们写这篇文章当然不是说Javascript不好,只是说它有些特性和我们通常理解的面向对象的语言有些差别。而这些差别很有可能和你所想的有很大的差别。但并不是说他们就不好,从另外一个方面来讲,假如你习惯了,也许这也是Javascript独特的魅力。
1. 三个等号
假如你学过别的编程语言,比如Java之类的,你肯定知道一个等号表示赋值,两个等号表示比较。但是在Javascript中,你会发现这里不仅有两个等号,还有三个等号===。我们应该使用哪个呢?他们之间有什么差别的呢?其实两个等号,简单说就是只比较他们的值,而不管他的类型,比如:
if('2' == 2) {} // true
这里会返回true,因为他们的值是相等的,哪怕他们是不同的类型。
再看下面这个例子:
if('2' === 2) {} // false if(2 === 2) {} // true
在三个等号的情况下,上面一个例子会发现’2’和2的类型不同,所以就会直接返回false。也就是说三个等号需要是同样的类型,然后才进行值的比较。
通常来说,会推荐使用三个等号来进行比较。
2. 很多创建对象的方法
在Java或者C#中,你会有一个类,然后基于类创建对象。而在Javascript中,则更加灵活,你可以使用一下方法来创建对象:
- 使用类:Javascript中也有class,你可以定义相应的方法,域,getter, setter的调用等等。下面就是一个例子:
class Person { constructor(n) { this.name = n; } getName() { return this.name; } }
- 对象字面量(Object Literal): 你可以不定义类就创建对象,你只要使用{}就可以了,如下所示:
const person = { name: 'chris', city: 'location', getAll() { return `${this.name} ${this.city}`; } }
- 使用 Object create:你可以使用object.create()来创建对象,他是基于现有对象的prototype来创建的。参见下面的例子:
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
3. 块语句,看起来好像没有作用域
一些块语句,比如if, for, while等等,并不会创建一个本地的局部域。这也就意味着你在他们里面创建的变量,在他们外面是可以访问的。比如:
for (var i =0; i< 10; i++) { console.log(i); } console.log(i);
这里,console.log(i)会打印10,是不是很惊喜,为什么这个i在for循环之外还能访问呢?假如你去问Javascript的创始人Brendan Eich,他回答你,这就是个feature,哈哈。
假如你想让Javascript这个特性和别的你熟悉的语言类似的话,那么你可以使用let或者const,像下面这样:
for (let i = 0; i< 10; i++) { console.log(i); } console.log(i);
这样之后,你会得到i没有定义的错误。为什么会这样,因为let允许你定义只在某一个块中存在的变量。所以,这就是使用let而不是var的一个典型场景。
4. 上下文中,this的值是什么
你可能听说过一个玩笑,就是根本没有知道this是什么,假如一个空文件,那么this是一个全局的语境。比如下面这个代码:
global.name = "cross"; function someFunction() { console.log(this.name); } someFunction();
上面代码中,我们把name赋给global变量,这个this的值就从global的语境中得来。
我们再来看下面这个代码:
var object = { name: 'chris', getName() { console.log(`${this.name}`); } } object.getName();
在这里,this就是这个object本身了,这里我们知道name是什么,比如例子中就是chris。
改变语境
我们可以改变this的指向。Javascript中有几个方法可以用来改变this的指向,他们是bind(), call()以及 apply()。看下面这个例子:
global.name = "cross"; var object = { name: 'chris', getName() { console.log(`${this.name}`); } } function someFunction() { console.log(this.name); } someFunction();
现在someFunction中的this是指向global的,我们可以通过调用上面提到的方法来改变这个this,让他指向object中的this:
someFunction.bind(object)(); someFunction.call(object) someFunction.apply(object)
现在他就会打印出chris而不是cross了。
这三个方法的调用其实还是有一些差别的,不过在我们这里关于this的改变,还是一样的。
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
这里是因为你使用了new函数来创建的时候,this就发生了改变。那如何解决这个问题呢:
方案1: this = that
一个解决这个问题的方法是通过一个that来保存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'
用箭头函数来替换原来的函数,也是可以解决这个问题的。
方案3:使用闭包
第三种方案是使用闭包。这里我们没有使用new,也没有使用this,代码如下:
function Person() { var name = 'chris'; const getName = () => { return name; } return { getName } } const person = Person(); console.log(person.getName()) // 'chris'
这个方案中,我们既没有使用new也没有使用this,这大概是最像javascript的使用方法了。
方案4: 把方法放到prototype中
我们可以这样写:
function Person() { this.name = 'chris'; } Person.prototype.getName = function() { return this.name; } const person = new Person(); console.log(person.getName()) // 'chris'
这是一个很不错的方法。他不仅仅解决了this的问题,也可以保证这个方法只被创建一次,而不是每一个实例都创建一次。
方案5: 使用一个类
代码如下,他和方案4差不多:
class Person { constructor() { this.name = 'chris' } getName() { return this.name; } } const person = new Person(); console.log(person.getName()) // 'chris'
由于篇幅所限,其实很有很多别的情况会遇到this的问题,这里就不再一一详细介绍了,不过还是希望这篇文章能够抛砖引玉,让你有一些解决相关问题的想法。
5. Const可以工作,但是可能和你想的不太相同
我们都理解const这个关键字,一提到这个关键字,你会想到什么呢?不可以改变等等,对吧?我们来看下面这个例子:
const PI = 3.14 // exactly :) PI = 3;
假如你这样写,那么就会有这个错误:Assignment to a constant variable
哇,和我们想象的一样耶,开心。OK,那么我们再来看看下面这个例子:
const person = { name: 'chris' } person.name = 'cross';
这个代码竟然没有报任何错误,为什么会这样,不是说const表示不能改变吗?其实const只是说这个引用是只读的,也就是说这个引用不能被重新赋值。而不是说他的值不能改变,我们来看下面这个例子:
const person = { name: "chris", }; person = { name: 'chris' }
这个代码就会报出你想的错误了:Assignment to a constant variable
那我们有没有办法让person不能被改变呢,有的,我们可以使用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
这个例子中的town还是可以被改变的。那该怎么办呢?可能需要一个深度的freeze函数?也许我们需要仔细考虑一下,我们为什么需要这个,我的意思是大多数时候,我们的const应该是比较初级的变量,而不应该这么复杂,也许这是一个更好的答案。
6. 函数调用之后存在的生命周期
我们来看下面这个代码:
function aFunction() { let name = 'chris'; console.log(name) // prints chris } console.log(name)
没有什么特别的,在第二个打印name的地方我们并不知道name是什么,因为他已经在函数外面了。下面我们来稍微修改一下这个函数:
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());
第一个getName返回chris,然后我们调用setName,然后第二个getName会返回cross。咦,咋一看,是这样啊,这就是我想要的啊。但是我们再仔细想一下,我们其实都是调用的函数,而name是其中的局部变量,理论上我们前面使用的setName应该对后面没有影响才对啊。这大概就是Javascript的强大之处了。
参考文章: https://dev.to/itnext/5-things-that-might-surprise-a-javascript-beginner-oo-developer-1nje
Recent Comments