这一篇笔记主要记录下ES6版本的Javascript的入门学习笔记,只记录了自己了解的基础知识,实际用这门语言的时候还是要多查阅文档。
顺便推荐几个github上收藏量比较高的JavaScript学习仓库:
getify/You-Dont-Know-JS: A book series on JavaScript. @YDKJS on twitter. (github.com)
trekhleb/javascript-algorithms: 📝 Algorithms and data structures implemented in JavaScript with explanations and links to further readings (github.com)
airbnb/javascript: JavaScript Style Guide (github.com)
1. JavaScript基础 HTML中的JavaScript脚本代码必须位于 <script>
和</script>
标签之间,可以在<head>
中,也可以在<body>
中。
放在<head>
中的脚本一般是定义一个JavaScript函数,后续在<body>
中引用;或者也可以像前面的CSS一样从外部引入,需要在<script>
标签的src
属性中设置JS文件的吧位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script > function myFoo ( ) { document .getElementById ("test" ).innerHTML ="修改HTML当前标签的内容" ; } </script > </head > <body > <p id ="test" > 这是一个id为test的段落</p > <button type ="button" onclick ="myFoo()" > 修改</button > <script src ="js/index.js" > </script > </body > </html >
以上是个简单的例子,点击按钮后可以改变id
属性为”test”的标签的内容,可以看到JavaScript是如何控制网页行为的。
JavaScript可以用console.log()
的方式将输出数据写入控制台;或者用window.alert()
的方式将输出结果弹窗显示,方便起见后面的例子都用console.log()
将结果写入控制台(浏览器中按F12,点击控制台)。
我是python入门的,新学的编程语言也都会和python进行对比,JavaScript对缩进的要求不像python那么严格,因为python以缩进(4个空格)区分代码块,而JS以左右花括号区分代码块,换行的缩进建议为两个空格 。
在ES6版本之后,官方建议使用const(声明常量)
和let(声明变量)
代替var
进行变量声明和赋值,var作用域是函数(指函数内) ,而const和let是块级作用域(左右两个花括号之内) ,以下是作用域的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function example ( ) { let x = 10 ; if (true ) { let x = 20 ; console .log (x); } console .log (x); const y = 30 ; var z = 50 ; if (true ) { var z = 60 ; console .log (z); } console .log (z); } example ();
再说明下这里的var
作用域是函数内,所以和python一样,一个函数内定义的变量名不会影响到其他函数(除非你在函数外定义,那作用域就是全局)。
不仅仅是作用域的区别,在JavaScript中有个很有意思的概念**Hoisting(声明提升)**。我们知道python是一种顺序执行的语言,从上到下一行一行执行代码,JavaScript某种程度上也是顺序执行,但是可以被打破。
举个例子,var以及函数 的声明 会被提前到最近的作用域的最前面(const和let不会),但是赋值语句没有被提前 ,这就意味着我们可以在声明变量/函数之前使用变量/函数,但是变量的值就会成为undefined
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 console .log (x); var x = 5 ;foo (); function foo ( ) { console .log (x); } example ()function example ( ) { console .log (y); const y = 5 ; }
在上面的示例中,变量 x
和函数 foo
的声明被提升到作用域的顶部。因此,即使在它们的声明之前使用,代码也不会引发错误。但是,变量 x
的值在声明之前是 undefined
,而后才被赋值为5。还有一点,函数声明提升的优先级高于变量声明提升 ,这意味着函数声明会覆盖同名 的变量声明。
为了不引起混淆,在使用变量和函数前都要先进行声明,对于变量的声明就用不会被提升的const和let。
2. 数据类型 上面列了些javascript和python带给我的直观区别,要入门一门语言还是要从最基础的数据类型开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const username = "Phantom" ;const age = 26 ;const iscol = true ;const x = null ; const y = undefined ; const n = Symbol (); console .log (typeof x); const z = null ;console .log (z instanceof Object ); console .log (z === null ); const symbol1 = Symbol ('description' );const symbol2 = Symbol ('description' );console .log (symbol1 === symbol2);
3. 对象和解构赋值 要明确一点,JavaScript和python一样都是面向对象 的编程语言。JavaScript的对象由花括号分隔,在括号内部,对象属性以名称和值对的方式来定义,属性之间逗号分隔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const person = { name : 'Phantom' , age : 26 , hobbies : ["music" , "sing" , "dance" , "basketball" ], address : { street : "tarim street" , city : "tarim" , }, sayHello ( ) { console .log (`Hello, my name is ${this .name} .` ); } }; person.sayHello (); console .log (person.name , person.age );
JavaScript中对赋值运算有个比较有意思的拓展,叫做解构赋值 ,可以对数组或者对象进行模式匹配,变量一个萝卜一个坑对应进行赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let [a, b, c] = [1 , 2 , 3 ]console .log (a, b, c) const person = { firstname : "Alex" , lastname : "Xie" , age : 18 , hobbies : ["music" , "sing" , "dance" , "basketball" ], address : { street : "tarim street" , city : "tarim" , }, }; const { firstname, lastname, address : { city }, } = person; console .log (city);
在这里,我们通过解构赋值从 person
对象中提取了 firstname
、lastname
和 address
属性,并将 address
属性解构为 city
变量。由于 person
对象的 address
属性是一个对象,而我们只关心其中的 city
属性,所以通过解构赋值的方式将 city
属性提取出来。
整个过程相当于用一个同名变量将值从person
对象中提取出来。
4. 常用的内置方法 方法部分就比较多了,这里就列举常见的,真正需要的时候得查手册。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const username = "Phantom" ;console .log (username.length ); console .log (username.toUpperCase ()); console .log (username.substring (0 , 3 ).toUpperCase ()); console .log (username.includes ("P" )) console .log (username.startsWith ("n" )); const test_txt = "p h a n t o m" ;console .log (test_txt.split (" " )); const numbers = new Array (1 , 2 , 3 , 4 , 5 ); const fruits = ["apple" , "banana" ]; console .log (numbers); console .log (fruits[1 ]); fruits.push ("watermalon" ); console .log (fruits); fruits.pop (); console .log (Array .isArray (fruits)); console .log (fruits.indexOf ("apple" ));
5. 条件语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const n = 20 ;const m = 10 ;if (n === 10 ) { console .log ("n is 10" ); } else if (n > 10 || m > 10 ) { console .log ("n is greater than 10 or m is greater than 10" ); } else { console .log ("n is less than 10 and m is less than 10" ); } const z = 11 ;const color = z > 10 ? "red" : "blue" ; console .log (color); switch (color) { case "red" : console .log ("color is red" ); break ; case "blue" : console .log ("color is blue" ); break ; default : console .log ("color is not red or blue" ); }
6. 循环语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 for (let i = 0 ; i < 10 ; i++) { console .log (`for loop number: ${i} ` ); } let l = 0 ;while (l < 10 ) { console .log (`while loop number: ${l} ` ); l++; } const array = [1 , 2 , 3 ]for (let i = 0 ; i < array.length ; i++) { console .log (array[i]); } for (let i of array) { console .log (i); }
同样有continue
和 break
是用于控制循环流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (let i = 0 ; i < 5 ; i++) { if (i === 2 ) { continue ; } console .log (i); } let i = 0 ;while (true ) { if (i === 3 ) { break ; } console .log (i); i++; }
7. 类和构造函数 JavaScript也使用 class
关键字来声明类,类名通常使用大写字母开头。类的方法定义和python类的方法定义是一样的,都是使用普通函数的语法。
1 2 3 4 5 class MyClass { myMethod ( ) { } }
和python的构造函数使用方法名__init__()
定义类似,JavaScript的构造函数是类的一个普通方法,使用 constructor
关键字定义。Python 的构造函数可以接受任意数量的参数,包括 self(指向类的实例)作为第一个参数 。在构造函数内部,可以使用这些参数来初始化实例的属性。而JavaScript 的构造函数使用普通的函数参数来接收传递的值,没有特殊的self参数 。
演示一下两者构造函数的差异:
1 2 3 4 5 6 7 8 9 class MyClass : def __init__ (self, arg1, arg2 ): self.arg1 = arg1 self.arg2 = arg2 my_instance = MyClass("Hello" , "World" ) print (my_instance.arg1) print (my_instance.arg2)
1 2 3 4 5 6 7 8 9 10 11 class MyClass { constructor (arg1, arg2 ) { this .arg1 = arg1; this .arg2 = arg2; } } const myInstance = new MyClass ("Hello" , "World" );console .log (myInstance.arg1 ); console .log (myInstance.arg2 );
new
关键字用于创建一个类的实例对象,比如上面的例子,通过new
关键字,调用了MyClass
类的构造函数,并且传递了参数"Hello"
, "World"
,将构造函数的this
绑定到新创建的对象,最后,new
关键字返回了新创建的MyClass
实例对象myInstance
。
constructor
关键字也不是一定要显示声明的,需要理解构造函数是用来创建和初始化对象的特殊函数,在 JavaScript 中,如果一个函数被用于创建对象,它就被认为是构造函数。
python的构造函数不允许显示返回值 ,只负责初始化实例状态。而 JavaScript 的构造函数可以显式返回一个对象 ,如果返回的是一个对象,则该对象将作为实例创建的结果,否则将返回新创建的实例(上面的例子就是)。
1 2 3 4 5 6 7 8 9 10 11 class MyClass { constructor ( ) { this .property = "Value" ; return { customProperty : "Custom Value" }; } } const myInstance = new MyClass ();console .log (myInstance.property ); console .log (myInstance.customProperty );
上面的例子中,构造函数使用了return
语句返回了一个对象{ customProperty: "Custom Value" }
。当我们使用new
关键字创建MyClass
的实例时,返回的结果不是新创建的实例对象,而是显式返回的对象,因此这个对象并没有 property
属性,而是具有 customProperty
属性。
8. 封装、继承和多态 既然JavaScript是面向对象的编程语言,就一定有面向对象的三大特征:封装、继承和多态。
8.1 封装(Encapsulation)
对象和函数:很明显JavaScript中的对象和函数可以用来封装数据和行为。对象可以包含属性和方法,函数可以封装一段可执行的代码,并且可以接收参数和返回值。
访问控制:通过使用闭包 或者WeakMap ,可以模拟私有属性和方法,从而实现对数据的隐藏和封装。
我们不能像python中一样用_
或者__
在属性或者方法上实现私有化,JavaScript提供了一些特殊的机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function MyFunction ( ) { let privateProperty = "Private Value" ; this .getPrivateProperty = function ( ) { return privateProperty; }; this .setPrivateProperty = function (value ) { privateProperty = value; }; } const myInstance = new MyFunction (); console .log (myInstance.privateProperty ); console .log (myInstance.getPrivateProperty ()); myInstance.setPrivateProperty ("New Value" ); console .log (myInstance.getPrivateProperty ());
在上述示例中,我们使用闭包创建了一个私有变量privateProperty
。通过在构造函数内部定义公有方法getPrivateProperty
和setPrivateProperty
,我们可以访问和修改私有属性(内部的两个方法访问修改了外部函数的变量)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const privateProperties = new WeakMap ();class MyClass { constructor ( ) { privateProperties.set (this , { privateProperty : "Private Value" }); } getPrivateProperty ( ) { return privateProperties.get (this ).privateProperty ; } setPrivateProperty (value ) { privateProperties.get (this ).privateProperty = value; } } const myInstance = new MyClass ();console .log (myInstance.privateProperty ); console .log (myInstance.getPrivateProperty ()); myInstance.setPrivateProperty ("New Value" ); console .log (myInstance.getPrivateProperty ());
Map
和WeakMap
都是ES6中新的数据结构集,一个对象由多个key-value键值对构成,在Map中,任何类型都可以做对象的key;而在WeakMap中,key必需是对象,所有的key都是弱引用,不用考虑垃圾回收。
在构造函数中,使用privateProperties
定义了一个WeakMap
实例。通过 privateProperties.set(this, { privateProperty: "Private Value" })
将当前实例对象作为键,将一个包含私有属性 privateProperty
的对象作为值存储在 WeakMap
中。
然后,通过在类的原型上定义 getPrivateProperty
和 setPrivateProperty
方法,访问和修改存储在 WeakMap
中的私有属性。
8.2 继承(Inheritance) 使用 extends
关键字来创建子类,并使用 super
关键字来调用父类的构造函数和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Animal { constructor (name ) { this .name = name; } speak ( ) { console .log (`${this .name} makes a sound.` ); } } class Dog extends Animal { constructor (name, breed ) { super (name); this .breed = breed; } speak ( ) { console .log (`${this .name} barks.` ); } fetch ( ) { console .log (`${this .name} fetches the ball.` ); } } const myDog = new Dog ("Buddy" , "Golden Retriever" );myDog.speak (); myDog.fetch (); console .log (myDog.name ); console .log (myDog.breed );
在上面的例子中,我们有一个父类Animal
和一个子类Dog
。子类Dog
使用extends
关键字继承了父类Animal
(python就不需要)。子类Dog
在构造函数中使用super(name)
调用了父类的构造函数,并传递了name
参数。
子类可以覆盖父类的方法,上面的例子就覆盖了父类的speak()
方法。在子类中,我们可以使用this
关键字引用当前实例的属性和方法,包括从父类继承的属性和方法。
8.3 多态(Polymorphism) JavaScript 是一种动态类型语言,变量的类型可以在运行时改变。这种动态性使得 JavaScript 具有一定的多态性。例如,多个对象可以对同一个方法做出不同的响应,根据对象的实际类型来确定要调用的方法(相同接口,不同实现 )。
举个例子,假设我们有一个 Animal
类,它有一个 makeSound
方法用于发出动物的声音。然后我们派生出两个子类 Dog
和 Cat
,它们分别重写了 makeSound
方法来发出不同的声音。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Animal { makeSound ( ) { console .log ("The animal makes a sound" ); } } class Dog extends Animal { makeSound ( ) { console .log ("The dog barks" ); } } class Cat extends Animal { makeSound ( ) { console .log ("The cat meows" ); } }
现在,我们可以创建不同的对象并调用它们的 makeSound
方法,它们会根据自己的实现发出不同的声音。
1 2 3 4 5 6 7 const animal = new Animal ();const dog = new Dog ();const cat = new Cat ();animal.makeSound (); dog.makeSound (); cat.makeSound ();
本质上就是子类对父类的继承方法的重写,实现调用一个接口具有不同的实现。
对于第8部分可能有些难以理解,可以结合前面python的面向对象编程笔记:
python自学笔记(3)——面向对象编程(上) - 我的小破站 (shelven.com)
python自学笔记(4)——面向对象编程(下) - 我的小破站 (shelven.com)
JavaScript还有很多高级用法,这里只记录了入门的一些基础,以后要用到再深入了解。