JavaScript基础

函数

参数

由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:

1
2
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9

传入的参数比定义的少也没有问题:

1
abs(); // 返回NaN

此时abs(x)函数的参数x将收到undefined,计算结果为NaN

要避免收到undefined,可以对参数进行检查:

1
2
3
4
5
6
7
8
9
10
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}

arguments

它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array

理解为一个参数列表,多用于判断参数个数。

rest

ES6引入的语法!

rest参数只能写在最后,前面用...标识

1
2
3
4
5
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}

用于接收剩下的参数,来替代arguments的复杂判断。

调用

apple

它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}

var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

利用apple实现装饰器

1
2
3
4
5
6
7
var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};

call

同apple,唯一区别是:

  • apply()把参数打包成Array再传入;

  • call()把参数按顺序传入。

比如调用Math.max(3, 5, 4),分别用apply()call()实现如下:

1
2
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

作用域

全局作用域

JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性

1
2
3
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

我们每次直接调用的alert()函数其实也是window的一个变量

局部作用域

函数内为局部作用域

for循环等块语句是全局作用域!要声明局部变量使用let关键字

命名空间

全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中

1
2
3
4
5
6
7
8
9
10
11
// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
return 'foo';
};

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。

解构赋值

(拆包)

1
2
3
4
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

快速获取对象的指定属性

1
2
3
4
5
6
7
8
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;

可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的

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
31
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined


// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

this指向问题

对象里的函数叫方法

  • 在对象的方法内,this指向被调用的对象。要保证this指向正确,必须用obj.xxx()的形式调用!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}

var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};

xiaoming.age(); // 25

// 外部调用,指向window
getAge(); // NaN

var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN
1
2
3
4
5
6
7
8
9
10
11
12
13
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};

xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)

修复的办法也不是没有,我们用一个that变量首先捕获this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};

xiaoming.age(); // 25

箭头函数修复了this指向的问题!

1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25

this总是指向词法作用域,也就是外层调用者obj

this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:

1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25

生成器

1
2
3
4
5
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}

generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。

调用generator对象有两个方法,一是不断地调用generator对象的next()方法,next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果donetrue,则value就是return的返回值。

第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done

对象

继承

JavaScript由于采用原型继承,我们无法直接扩展一个Class

JavaScript的原型继承实现方式就是:

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this

  2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;

  3. 继续在新的构造函数的原型上定义新方法。

1
2
3
4
5
6
7
// 继承实现方式
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}

class继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student {
constructor(name) {
this.name = name;
}

hello() {
alert('Hello, ' + this.name + '!');
}
}

// s
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}

myGrade() {
alert('I am at grade ' + this.grade);
}
}

高阶函数

map

map(fn)

1
2
3
4
5
let ns = [1,2,3];
let new_ns = ns.map(function(n){
return n * 2
})
// new_ns = [2, 4, 6]

reduce

reduce(fn, preDefault)

1
2
3
4
5
let ns = [1,2,3];
let new_ns = ns.reduce(function(preValue,n){
return preValue + n
}, 0) // 第一个值为0
// new_ns = 0+1+2+3 = 6

filter

filter(fn)

1
2
3
4
5
let ns = [1,2,3];
let new_ns = ns.filter(function(preValue,n){
return n >= 2
}) // 第一个值为0
// new_ns = [2, 3]

模块导入导出

ES6

因为直接引用script是导入到全局作用域,会导致变量名重复等问题。

解决:以模块为变量作用域,通过导入导出的方式来提供复用。

1
2
3
4
<!--这里的type必须是module,表示作用域限定与模块-->
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="ccc.js" type="module"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// aaa.js

var flag = true;
var num = 100;
// 1.1 导出变量
export {
flag, num
}
// 1.2 导出
export var len = 10;
// 1.3 导出
export function add(n1, n2){
return n1 + n2
}
// export default
// 导出一个变量,可以让导入者自己命名,但是一个文件只能导出一个export default
var address = "北京市";
export default address;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bbb.js

// 导入
import {flag, num} from "./aaa.js";
console.log(flag);
console.log(num);

import {len} from "./aaa.js";
import {add} from "./aaa.js";
console.log(add(len, num));
// 导入export default的变量
// 可以自命名,变量不用{}包裹
import addr from "./aaa.js";
console.log(addr);
1
2
3
4
5
6
// ccc.js

// 导入aaa中所有导出的变量
import * as hah from "./aaa.js";
// 通过 别名.变量名 使用
console.log(hah.num);

JavaScript基础
http://blog.rainna.xyz/2022/10/08/2022-10-08-Note/
作者
rainnalv
发布于
2022年10月8日
许可协议