Fork me on GitHub

js进阶知识点整理

js重要知识点

一下内容多来自《You Don’t Know JS》以及个人日常整理
算是自己学习JS的一个转存站吧毕竟个人脑子内存有限
记录下来方便日后回顾

纯函数与高阶函数

纯函数

  • 1.一类特别的函数: 只要是同样的输入,必定得到同样的输出
  • 2.必须遵守以下一些约束
    • a.不得改写参数
    • b.不能调用系统 I/O 的API
    • c.能调用Date.now()或者Math.random()等不纯的方法
  • 3.reducer函数必须是一个纯函数

高阶函数

  • 1.理解: 一类特别的函数
    • a.情况1: 参数是函数
    • b.情况2: 返回是函数
  • 2.常见的高阶函数:
    • a.定时器设置函数
    • b.数组的map()/filter()/reduce()/find()/bind()
    • c.react-redux中的connect函数
  • 3.作用:
    • a.能实现更加动态, 更加可扩展的功能

对象内属性名用[]包裹则变为变量的值

1
2
var a ='ax'
[a] === 'ax'

数组方法是否影响原数组

如果直接操作的是原数组,那肯定会改变。但是如果操作的是item这个参数,也就是回调函数的第一个参数,那么要看这一项是什么类型的值,基础类型的值则不会改变,引用类型的会改变

new操作符具体干了什么呢

其实很简单,就干了三件事情

1
2
3
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
  • 第一行,我们创建了一个空对象obj
  • 第二行,我们将这个空对象的proto成员指向了Base函数对象prototype成员对象
  • 第三行,我们将Base函数对象的this指针替换成obj,然后再调用Base函数,于是我们就给obj对象赋值了一个id成员变量,这个成员变量的值是”base”,

instanceof

instanceof 检测一个对象A是不是另一个对象B的实例的原理是:查看对象B的prototype指向的对象是否在对象A的[[prototype]]链上。如果在,则返回true,如果不在则返回false。不过有一个特殊的情况,当对象B的prototype为null将会报错(类似于空指针异常)。

相对于instanceof constructor更加严谨

fn.的形式是函数对象形式 fn()是调用模式

函数作为对象使用时函数为函数对象

伪数组转数组

1
var arr = Array.apply(null,item);

对象的属性是根据对象的基地址值进行偏移

var x = Object.create(null)

创建最干净的对象

函数

函数是javascript中的一等公民。javascript中不分普通函数和构造函数。
只存在函数的普通调用和构造调用。如果函数被普通调用,那即为普通函数。
如果函数被构造调用,那即为构造函数。

js中所有的传递都是值传递,没有引用传递

  • 基本数据类型总是通过值复制的方式来赋值/传递
  • 引用数据类型总是通过引用复制来完成赋值/传递

改变的是地址的间相连的线

堆空间栈空间的指向会断开指向其他基地址值(改变对象引用的时候)
基本类型没有堆空间
指向相同值会同步修改
指向改变不会影响前值


{} == {} false 引用类型对比的是地址


强制类型转换

ToPrimitive:它负责处理对象的基本化:

  • 检查该值是否有 toString() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换
  • 如果没有返回值不是基本数据类型
  • 检查该值是否有 valueOf() 方法,如果有并且返回基本类型值,就使用该值进行强制类型转换
  • 如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误

valueOf():

  • JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。
  • 你很少需要自己调用此函数; JavaScript 会自动调用此函数当需要转换成一个原始值时。
  • 默认情况下, valueOf()会被每个对象Object继承。每一个内置对象都会覆盖这个方法为了返回一个合理的值,
  • 如果对象没有原始值,valueOf() 就会返回对象自己

toString():

  • 对普通对象来说,除非自行定义,否则 toString() 返回 内部 属性[[Class]] 的值
  • 即调用Object.prototype.toString()
  • 如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值
  • 比如数组的默认 toString() 方法经过了重新定义,将所有单元字符串化以后
  • 再用 “,” 连接起来:

函数调用不会改变函数的作用域链,函数的作用域是声明时候创建的,不会被修改

作用域是在编译阶段创建的

  • 广义上来说:
    • 作用域是负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
  • 狭义上来说:
    • 作用域是程序代码中定义的一个区域,其控制着变量与参数的可见性及生命周期
  • 总结:

    • 作用域与变量的读取跟存储有着密不可分的联系。
  • 右查询(RHS):在变量所在的作用域链中找不到声明,浏览器引擎会抛出ReferenceError: a is not defined

  • 左查询(LHS):在变量所在的作用域链中找不到声明,浏览器引擎会在全局作用域内声明一个

函数每调用一次就会创建一个执行上下文栈(不会另创建作用域)压栈出栈按照先进后出的规则依次出栈

  • 1.在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 2.在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  • 3.在函数执行上下文创建后, 将其添加到栈中(压栈)
  • 4.在当前函数执行完后,将栈顶的对象移除(出栈)
  • 5.当所有的代码执行完后, 栈中只剩下window

出栈时没执行的代码要执行完毕后才算出栈完毕
压栈时(函数递归等情况下)会阻止后续代码逻辑的执行等当前状态出栈时再执行没有执行的逻辑

全局的执行上下文栈在页面关闭的时候出栈

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var i = 10
foo(1);
function foo(i) {
if (i == 4) {
return;
}
console.log('foo() begin:' + i);
foo(i + 1);
console.log('foo() end:' + i);
}
// 输出为: 0
// foo() begin:1
// foo() begin:2
// foo() begin:3
//此时i=4的逻辑return执行上下文出栈后面的执行上下文栈依次出栈
//出栈时执行未完成的逻辑
// foo() end:3
// foo() end:2
// foo() end:1

提升是指提升到本层作用域的最顶层
函数提升先于变量提升
变量的提升是声明的提升
函数的提升是整体的提升

if判断中function 会变为函数表达式
!千万不要在if else 这种块内部定义函数

1
2
3
4
5
6
7
8
9
10
11
test();
if(test){
function test(){
console.log(1)
}
}else{
function test(){
console.log(2)
}
}
//报错test is not a function

this相关

看函数调用位置上的调用形式

详细说明如何判断函数中的this

  • 1.正常情况: 执行函数的方式决定了函数中的this
    • 直接调用: fn() window
    • new调用: new fn() 新创建的对象
    • 对象调用: obj.fn() obj对象
    • call/apply调用: fn.call(obj) 第一个参数指定的对象
  • 2.特别情况:
    • bind()返回的函数: fn.bind(obj)() 第一个参数指定的对象
    • 箭头函数: 使用的外部的this(内部没有自己的this)
    • 回调函数
      • 定时器回调/ajax回调/数组遍历相关方法回调: window
      • dom事件监听回调: dom元素
      • 组件生命周期回调: 组件对象

this调用方式:

  • 独立调用

    • this使用默认绑定规则 绑给window 严格模式底下绑给undefined
  • 隐式调用

    • this使用隐式绑定规则 绑给离他最近的调用者

注意点:隐式丢失 this指向改变

只有函数对象有call,apply,find方法

  • call apply bind
    • this使用显式绑定规则 this绑给call apply bind指定的对象
    • call,apply
      • 区别:第二个参数,call:列表;apply:数组
      • 共同点:第一个参数都是改变this指向的,立即调用的
    • bind
      • 区别:bind:返回硬绑定函数,没有直接调用;(call;apply):立即调用的
      • 共同点:第一个参数都是改变this指向的
      • 返回一个新的函数,改变this指向,原函数没有发生变化

call,apply可以让任意函数给任意对象使用

  • new 调用
    • 函数是js中的一等公民,js中没有构造函数,只有函数的构造调用
    • Array的普通调用和构造调用 —>返回一个array实例
    • 包装类函数的普通调用—> 强制类型转化
    • 包装类函数的构造调用—> 返回包装类所对应实例
    • this—>构造出来的实例对象

优先级

new调用 > call apply bind > 隐式调用 > 独立调用

bind绑定后无法用call apply修改this指向只能给其添加新的变量和执行函数

如果你把 null 或者 undefined 作为 this 的绑定对象传入 call 、 apply 或者 bind ,
这些值在调用时会被忽略,实际应用的是默认绑定规则(window)

函数柯里化

即在函数声明或者创建的时候给函数设定默认值(这个默认值可以是任意性质的)

闭包

闭包:当函数能够记住并且访问自己的作用域链时就会产生闭包
当函数发生嵌套时,如果内部函数引用了外部函数的变量时,就回产生闭包
闭包是一个js引擎创建的c++对象,用来管理一些数据,内部函数引用了外部函数的数据

闭包的生命周期

  • 在wrap函数的执行上下文被创建时就已经创建inner的闭包
  • 闭包是由wrap函数创建

    • 由wrap裹函数将闭包交给inner函数的作用域
  • 闭包在wrap 和 inner的执行上下文出栈时不会被清除

    • inner = null 被清除
  • 当闭包的引用计数为0的时候,自动被收回

闭包的值在wrap创建初期确立在连续调用中闭包的值可能会被修改
但是在inner分开调用的时候闭包的值是不会被修改的

闭包的问题:会造成内存泄露进而会导致内存溢出

  • 闭包在函数创建的时候创建到内层函数的作用域里
  • 包裹函数执行多少次就创建了多少个闭包
  • 但是闭包的使用时根据函数调用的时候应用的有的闭包不会重复应用
  • 闭包与对应函数有绑定关系
  • 谁调用的内层函数应用的闭包就是那层包裹函数给予的闭包值

原型&&原型链

  • Function是自己构造的
  • 属性的查找是沿着原型链找的找不到返回undefined
  • 变量查询在作用域中查询

object

对象创建的6种方式:

  • Object构造
  • 字面量模式
  • 工厂模式
  • 自定义构造模式
  • 构造函数 + 原型链的混合模式
  • Object.creat方法

继承的两种方式:

  • 构造函数继承
    • 内存消耗多,不利于类型的创建
  • 原型链继承+构造函数继承
    • 原型链继承方法,构造函数继承属性
    • 内存消耗少
    • 注意每次原型链继承时 一定要修正constructor属性!!

属性描述符:

  • 获取对应属性的描述符:Object.getOwnPropertyDescriptor(obj,”name”);
    • 第一个参数:对应的对象
    • 第二个参数:对应对象的属性
  • 为对象新增或修改属性:Object.defineProperty()

defineProperty是函数对象拥有的

示例:

1
2
3
4
5
6
7
8
9
10
11
var obj={
name:"snw"
};
Object.defineProperty(obj,"age",{ // 配置对象
value:18,
writable:true,
configurable:true,
emumerable:true
});
console.log(obj); //Object {name: "snw", age: 18}
console.log(obj.age);//18
  • 是否可以修改属性的值:writable
    • 当writable为false时,对应的属性的值是无法修改的。
      • 在默认情况下:继续修改的话会静默失败
      • 在严格模式下:会报错
  • 决定属性是否可以配置:configurable

    • 当configurable为false时,对应的属性是无法重新定义的,无法删除的。但该对象是还可以继续添加属性的。
    • 不管是默认情况还是严格模式底下进行重新定义和删除都会报错
    • 注意一个小小的例外:当configurable为false时,writable可以进行重新定义,但只满足由writable:true ==> writable:false
      • 1.是否可以重新声明
        writable可以从true 变为 false
        value的值只归writable管
      • 2.是否可以删除
        delete
        configurable:false
        不能重新声明
        不能删除
        configurable:true
        能重新声明
        能删除
  • 是否可以枚举:enumerable(原型链内的属性默认是不可被枚举的)

    • 判断属性是否是可枚举的:(不会遍历原型链)obj.propertyIsEnumerable(“a”)
    • 获取可枚举的属性列表:(不会遍历原型链)Object.keys(obj)
    • 获取自身(own)所有的属性列表:Object.getOwnPropertyNames(obj)

三种描述符:

  • 属性描述符:修饰属性的属性
  • 数据描述符:具有value和writable的属性
  • 访问描述符(存取描述符):具有get和set的属性

get&set用法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.defineProperty(obj,"age",{
get:function(){ // 当读取属性值时自动调用,根据其他数据计算返回属性值
return this.__age__;
},
set:function(val){ // 当属性值发生改变时调用,监视属性值得改变,改变相关的其他数据
if(val>120){
val =120;
}else if(val<0){
val = 0;
}
this.__age__ = val;
}
})
obj.age=11;
console.log(obj.age);
console.log(obj.__age__);

属性的查找规则:

  • 先在对象的直接属性中找,找到就用,
  • 没找到上隐式原型链,找到就用,
  • 没找到返回undefined

属性的设置规则

  • 不管怎么样都是操作对象的直接属性
  • 讲完属性描述符后扩充这个规则

几种对象属性的设置方法:

  • 常量属性
1
2
3
4
5
6
Object.defineProperty(obj,"xxx",{
value:"yyy",
writable:false,
configurable:false,
enumerable:true
})
  • 禁止属性扩展
1
Object.preventExtensions(obj);

默认情况下
为对象添加新的属性会静默失败
严格模式底下
报错
注意:禁止对象扩展只是禁止了对象去扩展属性,
而之前的属性是可以进行重新定义或者删除的,
属性值也是可以修改的

  • 密封对象
1
Object.seal(obj);

密封之后禁止了对象去扩展属性,原有的属性不可以进行重新定义或者删除,但属性值是可以修改的

  • 冻结对象
1
2
3
4
5
6
7
8
9
10
11
12
13
//浅冻结
Object.freeze(obj);
//深度冻结 考虑一下是不是设计出了问题
for (item in obj){
//console.log(item)
Object.defineProperty(Object.prototype,item,{
writable:false,
configurable:false
})
if(obj[item] instanceof Object){
Object.freeze(obj[item]);
}
}

冻结之后禁止了对象去扩展属性,原有的属性不可以进行重新定义或者删除,属性值不可以进行修改

!in操作符是可以找到我们自定义在原型链的属性的
深层次的属性原型链上的属性都管不到
而所有的方法都可以影响对象的直接属性的
深层次的属性原型链上的属性都管不到

事件循环机制

同步代码和异步代码(初始化代码,回调代码)

回调代码:

  • DOM事件回调 -> DOM事件管理模块管理
  • AJAX请求回调 -> AJAX请求管理模块管理
  • 定时器回调 -> 定时器管理模块管理

定时器

  • js引擎调起异步线程把定时器按照延迟的时间后把定时器代码放入异步队列
  • 异步队列永远都是等待同步代码执行完毕JS引擎进行轮询时再调用的
  • 定时器的延时是放入异步队列的时间而不是执行的延迟
  • 其他异步代码(回调代码)同理

堆里执行回调代码,栈内存放对象等大的数据

事件以及回调以及AJAX都是在主线程里执行

等待事件轮询再调用