js随笔
本文最后更新于:2021年2月17日 下午
注:全片并未按照一定的顺序编辑,而是仅是相对无序的知识点的整理,参见目录
for-in
通常用于遍历对象
1 |
|
当然,js中数组也是一种特殊的对象,因此for-in
也可以像枚举对象属性一样枚举数组索引。但某些浏览器的实现就比较坑了,比如ie
,它会把数组原型的一些属性(函数名)也给遍历出来,如map
,find
,forEach
等。
ie
而chrome和edge的实现则比较友好
通常来说上述ie的情况不是我们希望遇到的,所以嘛,遍历数组还是用原生的for
或着forEach()
好一点
this
修改函数this指向: call()
,apply()
,bind()
手写实现
js中函数也是对象,因此在使用this时要注意作用域问题。如vue-cli3下,在methods中写多层嵌套函数时,内层函数使用this时会获取不到vue的实例变量。目前暂时想到的解决办法有两种吧
- 利用临时变量暂存, 然后进行参数传递,这里传的是引用(或者说类似于指针),因此可以对原变量进行修改
- 使用箭头函数,箭头函数内部的this指向的是外层对象(特殊性)
1 |
|
数组
虽然经常用js,但其实很多基本操作都不是很熟悉,比如对数组的操作..
slice()
slice( start, end )
切片,不会修改原数组
1
2
3var 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
3var 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
9var 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
11var 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) // objectsort()
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
7var 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
9var 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
23var 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
6var data = "Anonymous"
!function(_data){
console.log(_data)
}(data)
// Anonymous使用()将函数及函数后的括号包裹
( function() ()) ;
1
2
3
4
5
6var data = "Anonymous";
(function(_data){
console.log(_data)
}(data))
// Anonymous使用()包裹函数值
(function())()
1
2
3
4
5
6var data = "Anonymous";
(function(_data){
console.log(_data)
})(data)
// Anonymous
Promise
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,所有错误都可以被后面的catch
语句捕获。
使用示例
1 |
|
另外,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 |
|
1 |
|
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 defined1
2
3
4
5
6
7
8
9
10for(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 |
|
1 |
|
上述两种方法创建结果基本是一样的,所创建的新对象都继承自Object.prototype
属性访问
点号
.
如果属性名特殊的话可能无法以该方式访问中括号
[]
1 |
|
遍历and判断属性存在
**遍历: ** for
变量名
in对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var 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
2console.log('name' in obj) // true
console.log(name in obj) // ReferenceError: name is not defined
数据类型
基本数据类型
共5种:Number, String, Boolean, Null, Undefined
存放:栈空间
比较:直接对值进行比较
引用数据类型
- 对象
- 存放:堆空间
- 比较:比较引用的地址
example
1 |
|
示意图:

Actually, 基本/引用数据类型在进行比较时都是对栈空间的值进行比较
1 |
|
函数
1.函数参数
- 一一对应,多余的参数会被忽略
2.函数也是对象
**函数具有对象的一切特点 **
函数是功能更强大的对象,可以封装代码
创建函数的方式
函数声明的方式
拥有默认属性
name
,其值对应函数名,无法直接修改(除非修改函数名)1
2
3
4
5
6
7
8
9
10
11
12
13function 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
2var fun = new Function("console.log('It is Function!')")
fun() // It is Function!赋值表达式的方式
1
2
3
4
5var 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
11function 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
原型链
**hasOwnProperty()**可用于检测一个属性是存在于实例中(返回true
),还是存在于原型中(返回false
)
in则与上述不同,只要能通过该对象访问到给定属性时返回true
,无论是在原型中还是实例中
for-in返回原型中和实例中所有可通过对象访问、可枚举的属性。注意,原来不可枚举的,如toString()
,如果被重写了、并且没把[[Enumerable]]设置为false,也会在for-in
循环中返回
null与undefined
undefined派生自null,因此在经过操作数转换后二者判定为相等,相等性测试如下
1
2console.log(null==undefined) // true
console.log(null===undefined) // false变量在声明后会默认得到一个undefined值,因此代码中给变量初始化一个undefined值并没有意义。而null则不同,如果变量准备用于存放对象,那么你可以给它赋值为null,可以体现null作为对象空指针的惯例(c语言中对指针也是这么做的)
0.1+0.2 !=0.3
1 |
|
基于ieee754数值浮点计算的通病,c语言也有这个问题。尽量不要直接拿浮点数来进行比较(浮点数本身可能就不精确)
解决: 可以加个容差,比如 |0.1+0.2-0.3| <= 0.00001(允许的误差值,根据需求情况自己限定)
杂记
json:javascript object notation(符号),通常用于前后端通信。其他类型的语言也有使用json,如java,php等
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!