JS学习笔记

strict模式

如果一个变量没有没var申明,那么他就是全局变量,所以可以通过

'use strict'

来强制通过var申明变量

数组

slice()

它截取Array的部分元素,然后返回一个新的Array

1
2
3
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']

因为返回的是行的array,所以也可以用作拷贝

1
2
3
4
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();
aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; // false
  • push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉
  • 如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉
  • reverse()把整个Array的元素给掉个个,也就是反转
  • concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array

对象

  • 删除属性 delete obj.name
  • 如果我们要检测xiaoming是否拥有某一属性,可以用in操作符:
  • 要判断一个属性是否是obj自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:
    obj.hasOwnProperty(‘name’); // true

Map和Set

Map

Map是一组键值对的结构,具有极快的查找速度。

1
2
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95

初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:

1
2
3
4
5
6
7
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

Set

Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

1
2
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3

重复元素在Set中自动被过滤:

1
2
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}

iterable

用for … of循环遍历集合,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
alert(x);
}
for (var x of s) { // 遍历Set
alert(x);
}
for (var x of m) { // 遍历Map
alert(x[0] + '=' + x[1]);
}

function

arguments

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

1
2
3
4
5
6
7
function foo(x) {
alert(x); // 10
for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);

实际上arguments最常用于判断传入参数的个数。你可能会看到这样的写法:

1
2
3
4
5
6
7
8
9
10
11
12
// foo(a[, b], c)
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}
foo(1,2,3) // 1 2 3
foo(1,2) // 1 null 2

变量提升

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
但是这里只会申明,不会赋值.

1
2
3
4
5
6
7
'use strict';
function foo() {
var x = 'Hello, ' + y;
alert(x); // 输出 Hello, undefined
var y = 'Bob';
}
foo();

相当于

1
2
3
4
5
6
function foo() {
var y; // 提升变量y的申明
var x = 'Hello, ' + y;
alert(x);
y = 'Bob';
}

全局作用域

JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性.(在是浏览器的情况下)

如果是在Node.js或者是vscode中,是global.

关于this

先来个例子,

1
2
3
4
5
6
7
var laowang = {
name:"老王",
getName:function(){
return "输入:"+this.name
}
}
console.log(laowang.getName())

但是如果我们这些写就不行了

1
2
3
4
5
6
7
8
9
function _getName() {
return "输入:"+this.name
}
var laowang = {
name:"老王",
getName:_getName
}
console.log(laowang.getName())//输入:老王
console.log(_getName())//输入:undefined

这是因为this指向的是调用者,第一个调用者是laowang,但是第二个调用者是默认全局变量window,
接着改

1
2
var func = laowang.getName
console.log(func())

这样func还是一个函数指针,指向了_getName,并没有调用者

apply和call

这里可以使用js的一个关键字apply,

1
2
var func = laowang.getName
console.log(func.apply(laowang,[])) //第一个参数是调用者,相当于Lua中的self,第二个是[],表示空,类似Lua中的"_"

还有一个类似的关键字call

  • apply是打包参数为array传入
  • call是依次传入参数
    小例子:
    1
    2
    Math.max.apply(null, [3, 5, 4]); // 5
    Math.max.call(null, 3, 5, 4); // 5

装饰器

利用apply可以动态改变函数的行为

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

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

parseInt('10');
parseInt('20');
parseInt('30');
console.log(count); // 3

高阶函数

高阶函数:Higher-order function,参数中有函数指针的方法

1
2
3
4
function add(x,y,f){
return f(x) + f(y)
}
console.log(add(-12,-12,Math.abs))

map和reduce

map:Calls a defined callback function on each element of an array, and returns an array that contains the results

1
2
3
4
var arr = [1,23,4,2,3]
console.log(arr.map(function (s) {
return -s
})) //[ -1, -23, -4, -2, -3 ]

reduce:Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

1
2
3
4
5
var arr = [1,23,4,2,3]
var func = function (a1,a2) {
return a1+a2
}
console.log(arr.reduce(func)) //33

闭包

占坑-以后来填

箭头函数

箭头函数(Arrow Function)

1
x=>x*x

相当于

1
function(x){return x*x}

generator

generator(生成器),一个类似函数但是可以返回多次的东东.
generator由function*定义,除了return,还有yield来返回多次.来个栗子

1
2
3
4
5
6
7
8
9
10
function* ff(){
yield 1
yield 2
return "over"
}
var _f = ff()
console.log(_f.next())//{ value: 1, done: false }
console.log(_f.next())//{ value: 2, done: false }
console.log(_f.next())//{ value: 'over', done: true }
console.log(_f.next())//{ value: undefined, done: true }

也可以搞个迭代器:for … of

1
2
3
4
5
6
7
8
9
10
11
12
function* ff(){
yield 1
yield 2
return "over"
}

for (var t of ff()) {
console.log(t)
//输出
//1
//2
}

标准对象

获取类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* f(){
yield typeof 123; // 'number'
yield typeof NaN; // 'number'
yield typeof 'str'; // 'string'
yield typeof true; // 'boolean'
yield typeof undefined; // 'undefined'
yield typeof Math.abs; // 'function'
yield typeof null; // 'object'
yield typeof []; // 'object'
yield typeof {}; // 'object'
}
for( item of f()){
console.log(item)
}

对象包装

js中的numberbooleanstring都有包装对象

1
2
3
var n = new Number(123); // 123,生成了新的包装类型
var b = new Boolean(true); // true,生成了新的包装类型
var s = new String('str'); // 'str',生成了新的包装类型

但是如果去掉new,就不会生成包装类型,简单的规则:

  • 不要使用new Number()、new Boolean()、new String()创建包装对象;
  • 用parseInt()或parseFloat()来转换任意类型到number;
  • 用String()来转换任意类型到string,或者直接调用某个对象的toString()方法;
  • 通常不必把任意类型转换为boolean再判断,因为可以直接写if (myVar) {…};
  • typeof操作符可以判断出number、boolean、string、function和undefined;
  • 判断Array要使用Array.isArray(arr);
  • 判断null请使用myVar === null;
  • 判断某个全局变量是否存在用typeof window.myVar === ‘undefined’;
  • 函数内部判断某个变量是否存在用typeof myVar === ‘undefined’。

面向对象

原型

JS中没有所谓的,所有的都是对象,所谓的继承,只是把一个对象指向了另一个对象.

其实,所谓的面向对象,不就是基于回溯查找吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};

var xiaoming = {
name: '小明'
};

xiaoming.__proto__ = Student;
console.log(xiaoming.name)
console.log(xiaoming.height)
xiaoming.run()

但是在实现的时候,不要直接使用__proto__去改变一个对象的原型.
Object.create()可以传入一个原型对象,但是没有任何属性(废话…)

1
2
3
4
5
var xiaoming = Object.create(Student)
xiaoming.name = '小王'
console.log(xiaoming.name)
console.log(xiaoming.height)
xiaoming.run()

原型链

例如创建了一个对象Array

var arr = [1, 2, 3];

其原型链是:

arr ----> Array.prototype ----> Object.prototype ----> null

所以,如果原型链很长,势必会影响访问效率

构造函数

JS可以使用构造方法来创建一个对象,用法如下

1
2
3
4
5
6
7
8
function Student(name){
this.name = name
this.hello = function(){
console.log("Hello:"+this.name)
}
}
var laowang = new Student('老汪') //这里要开上new,如果没有new,他返回的就是一个undefined
laowang.hello()

原型链如下

xiaoming ----> Student.prototype ----> Object.prototype ----> null

用new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:

1
2
3
4
laowang.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(laowang) === Student.prototype; // true
laowang instanceof Student; // true

原型继承

来一张关于proto,prototype等概念的图
概念图

参考文档:JS教程