js随笔

本文最后更新于:2021年2月17日 下午

注:全片并未按照一定的顺序编辑,而是仅是相对无序的知识点的整理,参见目录

for-in

通常用于遍历对象

1
2
for (variable in object)
statement

当然,js中数组也是一种特殊的对象,因此for-in也可以像枚举对象属性一样枚举数组索引。但某些浏览器的实现就比较坑了,比如ie,它会把数组原型的一些属性(函数名)也给遍历出来,如mapfindforEach等。

ie

在这里插入图片描述

而chrome和edge的实现则比较友好

1607826464980

通常来说上述ie的情况不是我们希望遇到的,所以嘛,遍历数组还是用原生的for或着forEach()好一点

this

this到底指向啥?看完这篇就知道了!

修改函数this指向: call(),apply(),bind() 手写实现

js中函数也是对象,因此在使用this时要注意作用域问题。如vue-cli3下,在methods中写多层嵌套函数时,内层函数使用this时会获取不到vue的实例变量。目前暂时想到的解决办法有两种吧

  • 利用临时变量暂存, 然后进行参数传递,这里传的是引用(或者说类似于指针),因此可以对原变量进行修改
  • 使用箭头函数,箭头函数内部的this指向的是外层对象(特殊性)
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
32
data() {
value: 1
}
methods: {
Func1(){
console.log(this.value) // 1
func2(){
console.log(this) // undefine
}
}
}
// 临时变量 + 传参
methods: {
Func1(){
var value = this.value
func2(value){
console.log(value) // 1
}
}
}
// 箭头函数 关于箭头函数,MDN上是这么解释的
/* 箭头函数表达式的语法比函数表达式更简洁,并且没有自
己的this,arguments,super或new.target。箭头函数
表达式更适用于那些本来需要匿名函数的地方,并且它不能用
作构造函数。*/
methods: {
Func1(){
()=>{
console.log(this.value) // 1
}
}
}

数组

虽然经常用js,但其实很多基本操作都不是很熟悉,比如对数组的操作..

  • slice()

    slice( start, end )

    切片,不会修改原数组

    1
    2
    3
    var arr = [1,2,3,4,5]
    console.log(arr.slice(1,4)) // [ 2, 3, 4 ]
    console.log(arr) // [ 1, 2, 3, 4, 5 ], slice()操作不会修改原数组
  • splice()

    splice( index, howmany, newItem1, … , newItemX )

    剪切(若有newItem参数则会替换剪切部分),会修改原数组

    1
    2
    3
    var arr = [1,2,3,4,5]
    console.log(arr.splice(2,1,6)) // [3], 返回剪切掉的部分
    console.log(arr) // [ 1, 2, 6, 4, 5 ], 替换
  • filter()

    filter( function( currentValue, index, arr), thisValue )

    • function(必须),数组中的每个元素都会执行这个函数,如果返回值为 true时最终保留该元素;

    • currentValue(必须),代表当前元素的值。

    • 不会改变原始数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var arr = [1, 2, 3, 4, 2]
    function remove(arr, item) {
    let myArr = arr.filter((e)=>{
    return e != item
    })
    return myArr
    }
    console.log(remove(arr, 2)) // [1, 3, 4]
    console.log(arr) // [1, 2, 3, 4, 2]
  • forEach()

    forEach( function(currentValue, index, arr ), thisValue)

    • currentValue(必须): 当前元素

    • index: 当前元素的索引值

    • arr: 当前元素所属的数组对象

    • thisValue: 传递给function的”this”的值. 默认下会传递undefined(但我在nodejs下和chrome测试默认情况均不是undefined,奇怪)

      注:如果回调函数使用了箭头函数,那么thisValue是无效的(箭头函数没有自己的this值)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arr = [6, 4]
    arr.forEach(function(e, index, myarr){
    console.log(e) // 6 4
    console.log(index) // 0 1
    console.log(myarr) // [ 6, 4 ]
    console.log(myarr === arr) // true 可见传递的时原数组的引用,可以用myarr修改原数组
    console.log(this) // { str: 'test' }
    }, {str: "test"})

    /*Array、Object用typeof()函数返回值都为object,区分不了是否是数组类型*/
    console.log(typeof arr) // object
  • sort()

    sort() 排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /* 
    sort()
    1.会改变原数组
    2.排序规则是根据unicode编码表,逐位比较
    3.默认升序
    */
    var arr = ['c', 'a', 'd', 'b']
    var result = arr.sort();
    console.log(result) // [ 'a', 'b', 'c', 'd' ] 返回排序后的数组
    console.log(arr) // [ 'a', 'b', 'c', 'd' ] 原数组会被改变

    // 逐位比较
    var arr1 = ['c', 'aa', 'd', 'b']
    console.log(arr1.sort()) // [ 'aa', 'b', 'c', 'd' ]
    var arr2 = [3, 4, 22, 1]
    console.log(arr2.sort()) // [ 1, 22, 3, 4 ] 一位一位地比较,因此这里22在3的前面

    如果想按照数值来排序,则需要给sort()传入一个比较函数compareFn(一个回调函数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 可以接收参数
    /* 注意,不同浏览器对sort的传参顺序可能不同
    比如chrome下是a=4 b=6,火狐下是a=6,b=4
    问题不大,不会影响排序结果*/

    var arr = [6, 4]
    var result = arr.sort((a, b)=>{
    console.log('a=', a) // 4
    console.log('b=', b) // 6
    })


    var arr = [6, 4, 2]
    var result = arr.sort((a, b)=>{
    console.log('a=', a)
    console.log('b=', b)
    })
    // a= 4
    // b= 6
    // a= 2
    // b= 4

    虽然各个浏览器采用的排序算法不一样,但有一点是一样的

    • 比较函数返回a-b: 对应升序
    • 比较函数返回b-a: 对应降序
    1
    2
    3
    4
    5
    6
    // 升序排序
    var arr = [6, 4, 22, 3]
    var result = arr.sort((a, b)=>{
    return a-b
    })
    console.log(result) // [ 3, 4, 6, 22 ]
  • push(), pop()

    push(), pop() 向原数组末尾插入/删除元素(栈的入栈和出栈)

    1
    2
    3
    4
    5
    6
    7
    var arr = [1,2,3,4,5]
    console.log(arr.push(6)) // 6,返回值是新数组的长度
    console.log(arr) // [ 1, 2, 3, 4, 5, 6 ]

    var arr2 = [1,2,3,4,5]
    console.log(arr2.pop()) // 5,删除数组末尾元素,返回值是被删除的元素
    console.log(arr2) // [1,2,3,4]
  • shift(), unshift()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // shift() 删除并返回数组的第一个元素
    var arr = [1, 2, 3]
    console.log(arr.shift()) // 1
    console.log(arr) //[ 2, 3 ]

    // unshift()向数组开头插入一个或多个元素
    var arr2 = [1, 2, 3]
    console.log(arr2.unshift(4, 5)) // 5
    console.log(arr2.unshift(6)) // 6
    console.log(arr2) // [ 6, 4, 5, 1, 2, 3 ]
  • 应用

    • 栈:push()入栈, pop()出栈
    • 队列:push()入队, shift()出队

闭包(closure)

1.闭包的特点

  • 我们在函数外部操作了函数内部的值
  • 闭包对应的函数中的变量是常驻内存

2.产生闭包的条件

  • 函数嵌套

  • 子函数必须用到了外层函数的变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* example */
    var fun = (function(){
    let data = 0
    function changeData(){
    data += 1
    return data
    }
    return changeData
    })()

    var example1 = fun
    console.log(example1()) // 1
    console.log(example1()) // 2
  • 无法使用!创建的匿名函数构建闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
     var fun = !function(){
    let data = 1
    function getData(){
    return data
    }
    return getData
    }();
    console.log(fun) // false 感叹号被用作取反运算了。。。。
    console.log(fun()) // TypeError: fun is not a function

应用例子

  • 以学生为例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     var student = (function(){
    let data = 1

    return {
    getData: function(){
    return data
    },
    setData: function(newVal){
    data = newVal
    }
    }
    });
    // 学生 1
    let stu1 = new student()
    console.log(stu1.getData()) // 1
    stu1.setData(2)
    console.log(stu1.getData()) // 2
    // 学生 2
    let stu2 = new student()
    console.log(stu2.getData()) // 1
    stu2.setData(3)
    console.log(stu2.getData()) // 3
    // ...
  • 缺点

    内存泄漏: 内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

    上述例子中,变量data是常驻内存,如果使用完后没有及时释放就会造成内存泄漏

    • 解决办法:利用javascript的垃圾回收机制进行回收。只需在使用完data后,把外部调用闭包的变量(引用)都赋值为null,这样changeData就被垃圾回收机制当成垃圾对象进行回收,之后对应的data也会被回收,所占的空间也将被释放

      1
      example1 = null;

浅拷贝,深拷贝

https://blog.lzwzw.cn/posts/af017481.html

匿名函数

三种写法:

  • 使用!开头。

    !function(形参)(实参)

    1
    2
    3
    4
    // 一个简单得匿名函数eg:
    !function(_data){
    console.log(_data)
    }

    上述操作并没有执行该函数,要执行该函数只需要在末尾加上括号

    1
    2
    3
    4
    5
    6
    var data = "Anonymous"
    !function(_data){
    console.log(_data)
    }(data)

    // Anonymous
  • 使用()将函数及函数后的括号包裹

    ( function() ()) ;

    1
    2
    3
    4
    5
    6
    var data = "Anonymous";
    (function(_data){
    console.log(_data)
    }(data))

    // Anonymous
  • 使用()包裹函数值

    (function())()

    1
    2
    3
    4
    5
    6
    var data = "Anonymous";
    (function(_data){
    console.log(_data)
    })(data)

    // Anonymous

Promise

MDN上的解释

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,所有错误都可以被后面的catch语句捕获。

使用示例

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
32
33
34
35
36
37
38
39
40
41
42
// nodejs环境下可能需要加上这段,unhandledRejection事件,专门监听未捕获的reject错误
// process.on('unhandledRejection', (reason, p) => {
// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
// // application specific logging, throwing an error, or other logic here
// });

var foo = function(data){
return new Promise((resolve,reject)=>{
if(data > 0.5){
resolve("大于0.5")
}
else{
reject("小于0.5")
}
})
}

console.log("foo: ", foo) // foo: [Function: foo]

/*resolve*/
foo(0.7).then((result)=>{ // result: 大于0.5
console.log("result: ", result)
}).catch((err)=>{
console.log("err", err)
})

/*reject*/
foo(0.2).then((result)=>{ // err: 小于0.5
console.log("result: ", result)
}).catch((err)=>{
console.log("err: ", err)
})

/*reject'冒泡'性质传递*/
foo(0.1).then((result) => { // err: 小于0.5,并不会输出"middle"
console.log("result: ", result)
}).then(() => {
console.log("middle")
}).catch((err) => {
console.log("err", err)
})

另外,Promise 内部的错误不会影响到 Promise 外部的代码。通俗的说法就是“Promise 会吃掉错误”。

回调函数

一直以来都有一个误解,以为只有js有回调函数。。。后来才发现不是这样的,很多编程语言对“回调函数”(callback,或者说“call after”)都有相应的实现方式。 编程语言以不同的方式支持回调,通常使用子例程,lambda表达式,block或函数指针。

wiki上回调函数的解释:a callback is a reference to executable code, or a piece of executable code, that is passed as an argument to other code. ( 回调是对可执行代码或一段可执行代码的引用,该引用作为参数传递给其他代码 )

wiki上C语言和js实现回调的例子如下:

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
/* C */
#include <stdio.h>
#include <stdlib.h>

/* The calling function takes a single callback as a parameter. */
void PrintTwoNumbers(int (*numberSource)(void)) {
int val1 = numberSource();
int val2 = numberSource();
printf("%d and %d\n", val1, val2);
}

/* A possible callback */
int overNineThousand(void) {
return (rand()%1000) + 9001;
}

/* Another possible callback. */
int meaningOfLife(void) {
return 42;
}

/* Here we call PrintTwoNumbers() with three different callbacks. */
int main(void) {
time_t t;
srand((unsigned)time(&t)); // Init seed for random function
PrintTwoNumbers(&rand);
PrintTwoNumbers(&overNineThousand);
PrintTwoNumbers(&meaningOfLife);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* javascript */
function calculate(num1, num2, callbackFunction) {
return callbackFunction(num1, num2);
}

function calcProduct(num1, num2) {
return num1 * num2;
}

function calcSum(num1, num2) {
return num1 + num2;
}
// alerts 75, the product of 5 and 15
alert(calculate(5, 15, calcProduct));
// alerts 20, the sum of 5 and 15
alert(calculate(5, 15, calcSum));

C传递的是指针,js中传递的是引用。二者还是很相似的,所以C语言那个例子用js来写也是可以的。不过引用和指针二者的区别对我来说就有点复杂了,暂时还理不清楚。

let 和 var

  • 块级作用域(一对花括号{}即是一个块级作用域)。

    在let出现前,js没有函数没有块级作用域,只有函数作用域

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    var i = 9
    }
    console.log(i) // 9

    {
    let j = 9
    }
    console.log(j) // ReferenceError: j is not defined
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for(var i=0; i<10; i++){
    setTimeout(()=>{ // 先执行循环,i自增到10,之后再依次执行setTimeout
    console.log(i) // 10 10 10 10 10 10 10 10 10 10
    }, 0)
    }
    for(let i=0; i<10; i++){
    setTimeout(()=>{ // 每一个块作用域里的i值都不会受到外界影响,i值为对应的0~9
    console.log(i) // 0 1 2 3 4 5 6 7 8 9
    }, 0)
    }

== 和 ===

===是严格运算符, ==是相等运算符

  • ===的比较规则

    • 不同类型值: false
    • 同一类的原始类型值: 同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。
    • 同一类的复合类型值: 两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象。
  • ==的比较规则

    • 相同类型: 与===完全一样。
    • 不同类型值: 先将数据进行类型转换,然后再用===进行比较。

对象

创建对象

1
var obj = {} 
1
var obj = new Object()

上述两种方法创建结果基本是一样的,所创建的新对象都继承自Object.prototype

属性访问

  • 点号.如果属性名特殊的话可能无法以该方式访问

  • 中括号[]

1
2
3
4
5
6
7
var obj = {'123': '456'}

console.log(obj['123']) // 456
console.log(obj[123]) // 456

// console.log(obj.'123') SyntaxError: Unexpected string
// console.log(obj.123) SyntaxError

遍历and判断属性存在

  • **遍历: ** for 变量名 in 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var obj = {}
    obj.name = "鲲鲲"
    obj.sing = () => { console.log("sing") }
    obj.jump = () => { console.log("jump") }
    obj.rap = () => { console.log("rap") }

    /* 遍历 */
    for(let i in obj){
    console.log(i) // 属性名
    console.log(obj[i]) // 变量值(属性的值)
    }
    // name 鲲鲲
    // sing () => { console.log("sing") }
    // jump () => { console.log("jump") }
    // rap () => { console.log("rap") }
  • **判断: ** 判断对象是否存在某个属性可以使用 '属性' in 对象,注意要给属性名加上引号,否则属性会被当做变量处理

    1
    2
    console.log('name' in obj)  // true 
    console.log(name in obj) // ReferenceError: name is not defined

数据类型

基本数据类型

  • 共5种:Number, String, Boolean, Null, Undefined

  • 存放:栈空间

  • 比较:直接对值进行比较

引用数据类型

  • 对象
  • 存放:堆空间
  • 比较:比较引用的地址

example

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 123;
var b = a;
a++;
console.log("a=", a);
console.log("b=", b);

var obj1 = {}
obj1.name = '鲲鲲';
var obj2 = obj1;

obj1.name = '碧萝'
console.log("obj1: ", obj1)
console.log("obj2: ", obj2)

示意图:

Actually, 基本/引用数据类型在进行比较时都是对栈空间的值进行比较

1
2
3
4
5
6
var obj1 = {}
obj1.name = '鲲鲲';
var obj2 = {}
obj2.name = '鲲鲲';
console.log(obj1 == obj2) // false 比较地址
console.log(obj1.name == obj2.name)// true 属性name是基本数据类型,直接比较两者的值

函数

1.函数参数

  • 一一对应,多余的参数会被忽略

2.函数也是对象

  • **函数具有对象的一切特点 **

  • 函数是功能更强大的对象,可以封装代码

  • 创建函数的方式

    • 函数声明的方式

      拥有默认属性name ,其值对应函数名,无法直接修改(除非修改函数名)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function fun(){
      console.log("It is function")
      }
      fun.name = '鲲鲲'
      fun.name1 = '碧萝'
      fun.age = 18
      console.log(fun.name) // fun 属性name无法修改
      console.log(fun.name1) // 碧萝
      console.log(fun.age) // 18
      console.log(fun) // 函数体
      // ƒ fun(){
      // console.log("It is function")
      // }
    • 创建对象的方式(少用)

      1
      2
      var fun = new Function("console.log('It is Function!')")
      fun() // It is Function!
    • 赋值表达式的方式

      1
      2
      3
      4
      5
      var fun = function(){
      console.log('It is Function!')
      };
      fun() // It is Function!
      console.log(fun.name) // fun

作用域

1.全局作用域

1.1 以var声明:

  • 只要不是函数内声明的变量,都是全局变量

  • 浏览器环境下,全局变量(方法)都是window对象的属性(方法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script>
    var a = 10; // 全局 script中会预解析
    b = 11; // 全局 未声明就赋值,默认为全局变量归全局window所有,不会报错
    function fun(){
    var c = 12; // 局部
    d = 13; // 全局
    console.log(c); // 12
    console.log(d); // 13
    }
    console.log(c) // ReferenceError: c is not defined
    </script>

    <script>
    console.log(a) // 10
    console.log(b) // 11
    </script>
  • 生命周期:跟window一样(浏览器页面打开时创建–页面关闭时销毁)

2.局部作用域

**2.1 函数作用域: **

  • 作用范围:函数内部

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script>
    function fun(){
    var a = 10; // 局部
    b = 11; // 全局

    }
    fun()
    console.log("b=", b) // b= 11
    console.log("a=", a) // ReferenceError: a is not defined
    </script>
  • 生命周期:使用函数时创建,使用完后销毁(注意,声明函数时,变量并不会被创建)

2.2块级作用域

  • 作用范围:一对花括号{}内(通常一对{}就是一个块)

    1
    2
    3
    4
    5
    6
    {
    console.log(a) // undefined
    let a = 1;
    console.log(a) // 1
    }
    console.log(a) // ReferenceError: c is not defined

    注意,var在函数中声明的变量是局部变量,但在非函数的{}中,则是全局的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    var a = 1; // 全局
    }
    console.log(a); // 1

    function fun(){
    var b = 1; // 局部
    }
    console.log(b) // ReferenceError: b is not defined

构造函数

  • 函数名首字母大写 。不大写也不会报错,但这是约定熟成的规范吧。其他人看到大写字母能直接意识到这是个构造函数,方便理解

  • 使用new关键字创建实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function People(name, age) {
    this.name = name;
    this.age = age;
    this.say = function(){
    console.log("Hello everybody! My name is " + this.name+", I am "+this.age+" years old! ")
    }
    }
    var kunKun = new People('鲲鲲',3)
    console.log(kunKun) // People { name: '鲲鲲', age: 3, say: [Function] }
    kunKun.say() // Hello everybody! My name is 鲲鲲, I like playing basketball

原型链

MDN

在这里插入图片描述

**hasOwnProperty()**可用于检测一个属性是存在于实例中(返回true),还是存在于原型中(返回false)

in则与上述不同,只要能通过该对象访问到给定属性时返回true,无论是在原型中还是实例中

for-in返回原型中和实例中所有可通过对象访问、可枚举的属性。注意,原来不可枚举的,如toString(),如果被重写了、并且没把[[Enumerable]]设置为false,也会在for-in循环中返回

null与undefined

  • undefined派生自null,因此在经过操作数转换后二者判定为相等,相等性测试如下

    1
    2
    console.log(null==undefined) // true
    console.log(null===undefined) // false
  • 变量在声明后会默认得到一个undefined值,因此代码中给变量初始化一个undefined值并没有意义。而null则不同,如果变量准备用于存放对象,那么你可以给它赋值为null,可以体现null作为对象空指针的惯例(c语言中对指针也是这么做的)

0.1+0.2 !=0.3

1
2
0.1 + 0.2 = 0.30000000000000004
console.log(0.1 + 0.2 == 0.3) //false
  • 基于ieee754数值浮点计算的通病,c语言也有这个问题。尽量不要直接拿浮点数来进行比较(浮点数本身可能就不精确)

  • 解决: 可以加个容差,比如 |0.1+0.2-0.3| <= 0.00001(允许的误差值,根据需求情况自己限定)

杂记

json:javascript object notation(符号),通常用于前后端通信。其他类型的语言也有使用json,如java,php等


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!