JavaScript基础学习

简介

JavaScript遵循ECMASCript核心语法规范,同时引入DOM(Document Object Model)和BOM(Browser Object Model)概念。

引入JS

有至少两种方式可以使用JavaScript,一是在用<script></script>标签中直接编写JS代码;二是使用<script src="demo.js">引入单独的demo.js文件。

注释

有两种方式可以为JS代码添加注释,

  1. // 添加单行注释
  2. /* */ 添加多行注释

基本数据类型

JS中有七种基本数据类型,

  1. number,数字,不区分整型和浮点型
  2. string,字符串,即使用单引号或者双引号包围的文本。多个字符串可以使用’+'拼接成一个字符串。
  3. boolean,布尔,取值true或false。
  4. null,表示对象不存在或者被显示赋值为null,使用关键字null表示。
  5. undefined, 表示属性或者方法不存在,或者未赋值,即没有初始值,使用关键字undefined表示。
  6. symbol,ES6新引入,可以使用Symbol(data)和Symbol.for(data)创建symbol。前者每次调用都会生成不同的symbol;后者又称为全局Symbol,如果data值一样,则Symbol也是相等的。
  7. object,对象,使用{}定义。

当使用typeof判断类型时,typeof null返回object

变量

有三种定义变量的方式: varletconst,后两者是ES6新引入的。

  1. var, ES5使用的关键字,可以定义局部变量,也可以定义全局变量。
  2. let, 定义块级变量,可连续赋值。
  3. const, 实际定义的是常量,具有块级作用域。因为const定义的变量一旦被赋值,就不能更改,如果强行更改,会报TypeError异常。且const变量必须在定义时就进行赋值。

如果声明变量时缺少var、let或const,则该变量被视为全局变量,要尽量避免这种情况。

++和–运算符

又称自增和自减,顾名思义,就是分别将自身值加1和减1。

字符串

字符串拼接

使用+号进行拼接。

字符串插值

使用${placeHolder}进行占位,可以显示出placeHolder的实际值,效果上类似于字符串拼接,但是字符串必须使用` (反引号)包围,而不是引号。

let name = 'Designer';
console.log(`My name is ${name}`);

结果:

My name is Designer

多行字符串

使用``(反引号)包围多行字符串,省略了不停敲击\n的麻烦。

类型判断 typeof

打印变量类型。

let name = 'Designer';
console.log(typeof name);

结果:

string

条件语句

主要有ifswitch两大条件语句。

条件的产生

  1. 比较运算符:===恒等于,表示值是否相等;!==表示不等于;=====的主要区别在于,如果类型不同,前者直接返回false,而后者则会做一次类型转换,然后再比较,所以前者较严格。比如nullundefined在使用===比较时返回false,但在使用==比较时返回true。
  2. 逻辑运算符:&&(与)、||(或)、!(非)。
  3. 真值和假值,以下值均为假值,假值在条件语句中被解释成false
  • 0
  • 空字符串
  • null
  • undefined
  • NaN

if 语句

if … else if … else …

可以使用三元运算符condition ? express_when_true : express_when_false 改装 if ... else ... 语句,但是当有多个else if时这样改写反而更麻烦。

switch 语句

switch (condition) {
    case one:
        ...
        break;
    case two:
        ...
        break;
    ...    
    default :
        break;
}

循环

for 循环

for (let num = 0; num < 4; num++) {
    console.log(num)
};

for循环还有另一种更高级、更简洁的使用方式: for .. in, 他会遍历所有元素,不用担心越界问题,而且对访问对象属性同样可行。

const arr = [1, 2, 4, 5];

for (let i in arr) {
    console.log(arr[i]);
}

while 循环

let num = 0;
while (num < 4) {
    console.log(num);
    num++;
}

do...while 循环

let num = 0;
do {
    console.log(num);
    num++;
} while (num < 4);

循环可以嵌套使用; 也可以使用关键字break退出当前循环;

函数

使用function 关键字声明函数。

默认参数

ES6引入默认参数的概念,即在函数声明时同时为某些参数指定默认值,当函数调用时,如果这些参数未被赋值,则使用默认值。
比如:

//函数声明
function logName(name='Designer Feng'){
    console.log(name);
}

//函数调用
logName();

结果:

Designer Feng

返回值

使用return 关键字指明返回值,但是并不需要在函数头部对返回值类型进行声明,因为javascript本身也是弱类型语言。调用者可以准确的接收到该返回值。如果没有指定返回值,默认返回undefined

函数表达式与匿名函数

匿名函数:顾名思义就是没有名称的函数,但是其他部分如关键字function、参数列表和函数体等都与一般函数一样。

函数表达式:使用变量指向一个匿名函数,那么该变量本质上就具有了函数的功能。

如函数表达式的例子:

//函数式声明
const printName = function (name) {
    console.log('Hello, ' + name);
}
//函数式调用
printName('Designer_Codecademy');

输出:

Hello, Designer_Codecademy

箭头函数

使用胖箭头=>定义函数,并且省略function关键字,如下:

const myArrawFunction = (x, y) => {
    console.log(x + y);
};

使用时和普通函数的调用一样:

myArrawFunction(1, 4);

输出:

5

箭头函数的其他特点:

  1. 如果只有一个参数,小括号’()'可以省略;但是如果有0个或者多个参数,不能省略。
  2. 如果函数体只有一行代码,那么花括号’{}'和return语句都可以省略,如,
const add = number => number + number;

高阶函数

形参为函数或者返回一个函数,这样的函数称为高阶函数。

函数是作为参数传入高阶函数的,所以不能带括号,就像传递普通参数一样,否则就变成了多个函数调用。

如下,演示高阶函数和普通函数的差异:

const func1 = () => {
    return 1;
};

//高阶函数定义
const highOrderFunc = (funcParam) => {
    console.log(funcParam());
};

//普通函数定义
const normalFunc = (value) => {
    console.log(value);
};

//高阶函数调用,注意参数不带括号,函数本身就是入参。
highOrderFunc(func1);

//普通函数调用,先执行内部函数,返回结果作为外部函数的入参。
normalFunc(func1());

生成器 generator

JavaScript中的生成器和Python中的生成器很像,看起来像个函数,使用function*+yield定义,yield能使其运行中断并返回期望值。

定义和使用如下:

function* gen(x) {
    yield x + 1;
    yield x + 2;
}

const gene = gen(2);
console.log(gene.next());
console.log(gene.next());
console.log(gene.next());

输出:

{ value: 3, done: false }
{ value: 4, done: false }
{ value: undefined, done: true }

生成器只有当调用next()方法时才会执行,当遇到yield时返回,返回值是一个对象,当无值返回时也不会报错,只不过value变成undefined且done变为true。所以如果只想要有效的value值的话,要做一些判断。或者使用for...of 遍历生成器,他只会返回有效的value值,如下:

for (var i of gen(2)) {
    console.log(i);
}

输出:

3
4

函数总结

  1. 函数也是对象,所以也有对象该有的基本属性和方法,如toString();
  2. 可以将函数名赋给另一个变量,新变量的name属性值就是原函数的名称,且新变量可以直接作为函数调用。

作用域

和其他任何编程语言一样,变量都是有作用域的,分为全局作用域、局部作用域和块级作用域(ES6引入块级作用域)。

全局作用域

顾名思义,在程序的任何位置都能访问到该变量,定义在函数体外部的变量具有全局作用域,使用var声明。在web中,这样的变量实际上被绑定到window对象上了。为了减少全局变量名字冲突的问题,可以使用命名空间,即定义一个自己的对象,然后把变量绑定到该对象。如:

var Person = {};
Person.name = 'Feng';
Person.age = 18;

局部作用域

如果定义在函数体内的变量使用var声明,则该变量具有局部作用域,即无论何时声明的该变量,在函数体内的任何位置都是可以使用的(但是如果在未赋值前使用的话,值是undefined,但是不会报错。)。

块级作用域

使用{}包起来的范围叫做块,在该范围内如果使用letconst来声明变量,则该变量具有块级作用域,只能在该块中使用,且必须先声明后使用。

局部作用域与块级作用域的区别

前面提到过,当在函数中使用var声明变量时,变量在该函数内部任何位置都是可以访问的,无论在哪里声明,因为实际上该变量的声明被提升到了函数体首行,叫做变量提升,如下:

const arr = [1, 2, 3];
function print() {
    for (var i in arr) {
        console.log(arr[i]);
    }
    console.log(arr[i]);
}

print();

输出:

1
2
3
3

可以看出,尽管i是在for循环中声明的,但是for循环外仍然可以使用,因为i具有局部作用域,实际声明被提升到了函数体的首行,所以在哪个位置都能使用。

但是当我们把上面代码中的var改为let后,就会报ReferenceError: i is not defined,这个错误是for循环外的console.log(arr[i])引起的。之所以会报错,是因为let定义的变量具有块级作用域,不会进行变量提升,所以只在{}范围内有效(此时的块指for循环体)。所以如果把let的声明放在函数{}中声明,也是没有问题的(此时的块变成了函数体),如下:

const arr = [1, 2, 3];
function print() {
    let i;
    for (i in arr) {
        console.log(arr[i]);
    }
    console.log(arr[i]);
}

print();

数组

最简单的定义数组的方式是使用方括号[]

数组元素可以是不同的类型。

数组下标从0开始。

字符串可以看成是多个字符组成的数组,因此可以使用下标访问特定位置的字符,如:

const name = 'Designer Feng';
console.log(name[4]);

输出:

g

使用length属性获取数组长度,也可以为length重新赋值,这样会引起数组长度的变化。如果新的length值大于之前的值,则数组其它位置则自动填充空元素;如果新的length值小于之前的值,则会将数组中大于length的元素直接删除。所以最好不要直接修改length值,访问数组时也注意不要越界(虽然越界也不会报错)。

常用方法

  1. push(item), 向数组中添加元素。
  2. pop(),移除数组中最后一个元素,并且返回该元素。
  3. shift(),删除数组头部的元素,并返回该元素。
  4. unshift(item),添加元素到数组头部。
  5. indexOf(item),返回item的索引位置。
  6. slipce(start,end),切割数组(不含end),返回切割后的数组,不会修改原数组。
  7. splice(start, count,newItems),从start开始,删除count个元素,如果指定newItem,那么newItems会填充到删除的位置,达到替换的效果。如果start<0,则会从0开始;如果start>=.length,不会删除任何元素;如果count<=0,不会删除任何元素;如果count>=.length,也不会报错,但是会删除start后的所有元素。

如end>.length时的删除操作:

const name = ['Jack', 'Luis', 'Blues'];
name.splice(1, 6);

输出:

[ 'Jack' ]

使用’Feng’和’Designer’替换’John’和’Blues’:

const names = ['John', 'Blues', 'Jack', 'Jorge'];
names.splice(0, 2, 'Feng', 'Designer');
console.log(names);

输出:

[ 'Feng', 'Designer', 'Jack', 'Jorge' ]

内嵌数组

即数组元素本身又是数组,其实就是多维数组,如下是一个二维数组,

const arr = ['Luis', 'Neo', ['Jhon', 'David']];

访问元素’David’:

const david = arr[2][1];

数组元素迭代

forEach()

arr.forEach(func)

指定一个回调函数func,arr中的每个元素依次传入函数func执行;func有三个参数,分别是元素值、元素索引和数组本身,但是一般只保留第一个参数,因为对于数组遍历,第一个参数已经足够了。forEach返回undefined

const arr = [1, 2, 'ab'];
arr.forEach((element, index, myArray) => {
    console.log(`index = ${index}, element = ${element}`);
});

输出:

index = 0, element = 1
index = 1, element = 2
index = 2, element = ab

map()

arr.map(func) //与forEach类似,也是接受一个函数作为入参,将数组arr中的元素依次传入func,返回计算之后的新值。不同的是,map会返回一个新的数组,新的数组元素就是func计算后的值,所以func必须有返回值,如果没有指定返回值,则默认都是undefined,如下:

const arr = [1, 2, 3, 4];
  1. 指定返回值
const newArr = arr.map(element => {
    return element * element;
})
console.log(newArr);

输出:

[ 1, 4, 9, 16 ]
  1. 不指定返回值
const newArr = arr.map(element => {
    // return element * element;
    element*element;
})

输出:

[ undefined, undefined, undefined, undefined ]

filter()

arr.filter(func) //过滤器,将原数组arr通过func过滤后新生新数组返回。依次将arr中元素传给func,如果func返回true,则保留该元素,否则过滤掉,最终所有保留下来的元素组成新的数组。

findIndex()

arr.findIndex(func) //返回首次匹配的元素索引,匹配规则由func指定,返回true则匹配成功。

reduce()

arr.reduce(func[,initialValue])

  1. func接收两个参数作为输入;initialValue可选。
  2. 首次迭代时,如果intialValue未指定,则从arr中取出两个元素传给func;如果指定了intialValue,则只取一个元素,另一个使用initialValue;
  3. func返回一个值,作为下次迭代的第一个输入参数,然后再从arr中取一个元素,将这两个参数再次传给func。
  4. 依次类推,最后reduce返回计算后的总结果。

例子:

const arr = [1, 2, 3, 4];

未指定intialValue:

const result = arr.reduce((first, second) => {
    console.log(`first: ${first}, second: ${second}`);
    return first + second;
});

console.log('result:' + result);

输出:

first: 1, second: 2
first: 3, second: 3
first: 6, second: 4
result:10

指定了intialValue:10

const arr = [1, 2, 3, 4];
const result = arr.reduce((first, second) => {
    console.log(`first: ${first}, second: ${second}`);
    return first + second;
}, 10);

console.log('result:' + result);

输出:

first: 10, second: 1
first: 11, second: 2
first: 13, second: 3
first: 16, second: 4
result:20

mapforEachfilterreduce等高阶函数的回调函数实际上都能接收多个参数,但是一般我们只用关注必须的一个或两个参数,其它可选的参数不用管。

对象

javascript中对象使用{}定义,对象中的数据都是key:value形式,称为属性,key是字符串;value可以是任意类型。就像是JSON对象(JSON:JavaScript Object Notation,JavaScript对象表示法),只不过JSON表示的类型范围要小,只支持numberbooleanstringnullarray []object {},而且对key和value要求更严格。

可以使用JSON.stringify(obj)将一个JavaScript对象obj转换为JSON对象;或者使用JSON.parse(jsonObj)将一个JSON对象转换为JavaScript对象。

所有JavaScript对象均继承至object。

访问属性

有至少两种方式可以访问对象的属性:

  1. 使用.key,key只能是属性名称本身,就算是值与属性名称相同的变量也不行。
  2. 使用[key]

第一种方式更加简单,第二种方式更加严谨。因为如之前所述,key是字符串,就这表示key中可能包含空白符等特殊字符,如果此时使用第一种方式会出现字符串的截断,出现语法错误,但是第二种方式不会有此问题,如下,

const me = {
    'My Name': 'Designer Feng',
    'My Age': 18,
};

console.log(me["My Name"]);

可以使用for..in遍历对象中的每个属性。

更新属性

可以直接使用=为属性赋值,这会出现两种情况:

  1. 指定的属性已经存在,此时只会更新value值。
  2. 指定的属性不存在,添加新的key:value对。

可以使用delete操作符删除属性,如delete me['My Name']

方法

方法是一种特殊的属性,因为其属性值是函数。

使用key:func,来定义方法,其中func是一个标准的javascript 函数。ES6之后可以使用简写的形式(省略冒号和function关键字)来定义方法,如下:

const designer = {
    printAge(age) {
        console.log('Age is ' + age);
    }
};

designer.printAge(3);

Getter和Setter

getter和setter形式上像两种方法,getter用来获取属性值,setter用来给属性重新赋值。在其他高级编程语言中(如java),属性有明确的访问权限,比如私有属性只能在对象内部使用,如果想在外部也能访问,必须设置公开权限和getter和setter类型方法。

但是javascript的属性本质上是没有访问权限之说的,但是为了达到像其他语言一样的效果,所以约定在属性前加上_(下划线)表示私有属性,便于给其他开发者说明,该属性是一个私有属性(其实在对象外部仍能直接访问),并且也提供getter和setter方法。

getter

定义getter:

const obj = {
    _name: 'Feng',

    get getName() {
        return `My name is ${this._name}`;
    }
};

使用get关键字定义getter,getter方法不能携带任何参数,否则就会报SyntaxError异常;要在被访问的属性前加上this关键字,否则找不到该属性。

使用getter:

console.log(obj.getName);

getter的调用必须去掉(),就像是使用属性一样,因为javascript就是把getter和setter当作属性(虽然他们看起来确实是方法),这样就达到了隐藏原始属性的目的。

setter

定义setter

const obj = {
    _name: 'Feng',

    get getName() {
        return `My name is ${this._name}`;
    },

    set setName(newName) {
        this._name = newName;
    }
};

使用set关键字定义setter,只能指定一个参数。

使用setter

obj.setName = 'Designer Feng';

像给属性赋值一样直接调用,实际是将新值赋给了setter指定的属性。

类(class)和实例

在任何面向对象(OOP)的编程语言中,都有的概念,它用来定义一套模板,然后利用该模板可以生成多个相似的实例(在其它编程语言中实例也叫对象或者实例对象,但是在这里我们还是稍微区分一下,因为我们之前说过JavaScript的对象是通过{}来定义的),先有类,后有实例。一个模板,多个实例。JavaScript使用class定义类,使用new关键字生成实例。

面向对象语言的三大特性:封装、继承和多态。JavaScript有原型继承和class继承(ES6),这里主要关注了后者。

定义类

class Designer {
    //构造方法
    constructor(name) {
        this._name = name;
    }
    //getter
    get getName() {
        return this._name;
    }
    //普通方法
    updateName(newName) {
        this._name = newName;
    }
}

类的定义有以下几个特点:

  1. 使用class关键字定义类的名称。
  2. 使用constructor 定义类的构造方法,每次使用new生成对象时,该方法就会被自动调用。
  3. 像对象一样,可以定义setter和getter以及普通方法,并且每个方法之间不用逗号隔开。

继承 Inheritance

现实世界中许多事物都是有相似性的,但又不能完全把它们归为一类,比如猫和狗都是动物,有时候当你定义模板时,你需要保留猫和狗的相似性(如哺乳类,宠物等),但又要区分他们的不同特点时(如叫声、夜行性等),那么就会用到继承。

使用extends关键字指明父类(Animal)和子类(Dog或Cat)之间的关系:

//定义父类Animal
class Animal {
    constructor(name) {
        this._name = name;
    }
}

//定义子类Cat,继承至父类Animal
class Cat extends Animal {
    constructor(name, nocturnal) {
        super(name);//调用super
        this._nocturnal = nocturnal;//新的属性,是否夜行性动物
    }

    //新的getter
    get nocturnal() {
        return this._nocturnal;
    }
}

//使用
const cat = new Cat('Bob', true);
console.log(cat._name);
console.log(cat._nocturnal);

继承有如下特点:

  1. 必须在子类constructor的第一行调用super(其实就是调用父类的constructor,所以要注意传递的参数和父类constructor保持一致)。
  2. 子类继承父类中的属性和方法。
  3. 子类可以定义自己的属性和方法。

静态方法

在类中使用static关键字定义的方法。这样的方法只能使用类名.方法名进行调用,子类和实例都不能调用静态方法。

模块 Module

可以把一些常用的功能或特性整合到单独的文件中,这样就可以在其它地方直接引入该文件,而不必重复编写代码,即可复用(且可导出导入)的代码片段被称为模块。

导出模块

常规的定义一个模块方法:

  1. 定义一个有意义的对象,即该对象有属性或方法能够供其它模块使用。
  2. 使用module.exports或者export(ES6)导出该对象。

如下,module_test.js

定义对象:

let Bird = {
    color: 'Yellow',
    fly: function () {
        console.log('Bird is flying.');
    }
};

let Cat = {
    color: 'White',
};

导出模块:

//导出单个模块
module.exports = Bird;

//导出多个模块
module.exports = { Bird, Cat };

一个文件中是可以定义多个对象的,如果想把多个对象都当作模块导出,可以使用{}[]将多个对象包起来,实际上就是将这些对象作为元素重新组合成新的对象或者数组。

而新式的export语法允许在声明对象时就导出(Named Export),如下导出fly:

export function fly(){
    ...
};

导入模块

可以在其它文件中使用require(file)或者import(ES6)将模块引入到当前文件中,这样在模块中定义的对象属性和方法就能被直接使用。

当module_test.js只导出一个模块时,该如何导入和使用?

导出:

module.exports = Bird;

导入:

const bird = require('./module_test.js');
bird.fly();

当module_test.js导出多个模块时,该如何导入和使用?

导出:

module.exports = { Bird, Cat };

导入:

const birdAndCat = require('./module_test.js');
birdAndCat.Bird.fly();

如果导出时使用的是数组,那么导入时可以使用下标进行访问数组中的每个元素。还可以使用export asimport as 为对象起别名。

Node环境不识别exportimport

如果JavaScript运行在NodeJS环境时,当使用export进行导出和import进行导入时,会报SyntaxError。比如我使用的VSCode + Code Runner插件运行JS时就会有这样的问题,因为Code Runner运行在node环境中的。根本原因是NodeJS对ES6语法的支持并不十分完善,可以考虑更新NodeJS或者先不理会,使用可以识别的语法:

条目 Node 浏览器
模块规范 CommonJS ES6
导出 modules.exports;exports export; export default
引入 require import; require

浏览器兼容性

ES6相对于ES5的ECMAScript版本,增加了许多特性,如letconst、字符串插值等,使得代码编写更加简洁和高效,但并不是所有浏览器都能很好的兼容这些特性,可以访问caniuse 网站查看主流浏览器对新特性的兼容性。

为了解决这样的问题,可以使用预处理工具将ES6语法转换为ES5,如Babel等。将一种语言转化为另一种语言的操作有一个专有术语,叫做TRANSPILATION

创建Babel环境(在工程目录下执行):

  1. npm init, 创建package.json文件。
  2. npm install babel-cli -D, 下载Babel命令行工具。
  3. npm install babel-preset-env -D, 下载ES6到ES5的映射关系库。
  4. touch .babelrc, 新建配置文件,并添加以下对象:
{
  "presets": ["env"]
}
  1. 在package.json中添加build编译指令:src:要转换的ES6代码目录,-d lib:转换后的ES5代码目录
...
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "babel src -d lib"
}
  1. npm run build, 编译ES6代码到ES5,其实就是执行第5步中配置好的build脚本,然后将生成的ES5代码放到lib目录中。

HTTP请求

常见有四种HTTP请求:POST/GET/PUT/DELETE,其实就是对应数据库操作的增/查/改/删。把请求发送给服务器后,服务器按照请求类型操作数据库,然后返回数据给浏览器;

AJAX: Asynchronous JavaScript and XML。

GET

示例:

const xhr = new XMLHttpRequest();
//指定响应数据的类型
xhr.responseType = 'json';
//指定成功后的回调函数
xhr.onreadystatechange = () => {
    if (xhr.readyState === XMLHttpRequest.DONE) {
        //处理响应数据
        handleResponse(xhr.response);
    }
}
//发送请求
xhr.open('GET', url);
xhr.send();

POST

POST和GET操作基本一样,但是由于POST是把客户端数据传给服务器,所以会携带额外的数据。

xhr.open('POST', url);
xhr.send(data);

猜你喜欢

转载自blog.csdn.net/JerkSpan/article/details/83240298
今日推荐