高阶js
原型与原型链
导入
-
一切引用类型皆对象。数组是对象,函数是对象,对象还是对象。对象里面的一切都是属性,只有属性,没有方法。那么这样方法如何表示呢?——方法也是一种属性。因为它的属性表示为键值对的形式。
-
函数是对象的一种,因为通过instanceof函数可以判断。
var fn = function () { }; console.log(fn instanceof Object); // true -
对象都是通过函数创建的
var obj = new Object(); obj.a = 10; obj.b = 20; var arr = new Array(); arr[0] = 5; arr[1] = 'x'; arr[2] = true;
正文
总结:
每个对象的__proto__都指向其对于的构造函数的prototype,
任何一个函数的__proto__都指向Function.prototype
Function构造函数是一个对象,指向构造函数的构造函数的prototype Function.__proto__==Function.prototype
普通对象本身是一个对象,是由Object构造函数构造出来的
任何一个对象的__proto__都指向Object.prototype
Object.__proto__.__proto__==Object.prototype
- 每个函数都有一个
prototype属性。
- 这个prototype的属性值是一个对象(属性的集合),默认的只有一个叫做constructor的属性,指向这个函数本身。
Function.prototype.constructor==Function//ture
-
每个对象都有一个
__proto__属性,指向创建该对象的函数的prototype。即:fn.__proto__ === Fn.prototype-
这里的
__proto__成为“隐式原型”function Fn() { } Fn.prototype.name = '张三'; Fn.prototype.getYear = function () { return 1988; }; var fn = new Fn(); console.log(fn.name); console.log(fn.getYear()); //Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。
-
-
Object.prototype确实一个特例——它的__proto__指向的是null,切记切记!(
Object.__proto__.__proto__==Object.prototype) -
Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的。所以它的__proto__指向了自身的Prototype。
即:
Function.__proto===Function.prototype(foo.__proto__===Function.prototype===Function.__proto__) -
Function.prototype指向的对象,它的__proto__也指向Object.prototype
因为Function.prototype指向的对象也是一个普通的被Object创建的对象
-
Instanceof的判断规则是:(A instanceof B)沿着A的proto这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
-
原型链:访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链
console.log(foo.__proto__.__proto__ === Object.prototype);//true
console.log(Function.__proto__ === Function.prototype);//true
console.log(foo.__proto__ === Function.prototype);//true
console.log(foo.__proto__ === Function.__proto__);//true
function foo() { }
console.log(foo instanceof Function);//true
console.log(foo instanceof Object);//true
//构造函数
function Foo(a, b) {
this.a = a;
this.b = b;
}
//实例化函数
var f1 = new Foo('qq', 'ww')
console.log(f1.__proto__ == Foo.prototype);
console.log(Foo.prototype.__proto__ == Object.prototype);
console.log(Foo.__proto__ == Function.prototype);
console.log(Foo.__proto__.__proto__ == Object.prototype);
console.log(Object.__proto__ == Function.prototype);
console.log(Function.prototype == Function.__proto__);
-
原型的灵活性:对象属性可以随时改动。如果感觉当前缺少要用的方法,可以自己去创建。
-
如何区分一个属性是在本身还是在原型上呢?
Object.prototype.hasOwnProperty(item)尤其是在for in遍历一个对象时,考虑是否需要原型上的属性
继承
-
原型继承
-
盗用构造函数
-
组合继承
-
原型式继承
-
寄生式继承
javascript中的继承是通过原型链来体现的。
js执行机制
进程线程协程
-
进程:
-
计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位(cpu资源分配的最小单位)。
-
会在内存空间里形成一个独立的内存体
-
状态:就绪、运行、阻塞
-
-
线程:
- cpu资源调度的最小单位,被包含在进程之中,有单线程和多线程。
-
一个进程可以包含多个线程,每条线程执行不同的任务,但可以共享资源完成任务提高了资源使用效率以及系统效率
- 状态:就绪、运行、阻塞
-
对比:
-
进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
-
进程拥有独立的内存空间,线程则共享所在进程中的内存空间
-
进程之间切换开销较大,而线程间切换开销较小
-
-
协程:
- 比线程更小,由用户管理控制,多个协程可以运行在一个线程上面
并发&并行
-
并发:在某一时间段,几个程序在同一cpu上运行,但在任意一个时间点,只有一个程序在cpu运行
-
并行:两线程同时进行
并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以最关键的点就是:是否是 同时
同步&异步
- 同步任务:主线程上排队执行的任务,只有前一个任务执行完,才继续执行下一个
- 异步任务:不进入主线程而进入任务队列的任务。只有任务队列同时主线程,异步任务才进入主线程执行
常见异步操作
-
回调函数
-
同步回调,比如数组的一些方法(pop、unshift...)会立即执行完全执行完了才结束,不会放入回调队列中
-
异步回调,不会立即执行,放入回调队列来执行
-
-
事件监听
满足事件发生的条件才执行
-
发布订阅模式
-
promises对象
-
Promise是一个构造函数,用来封装一个异步操作并可以获得异步执行结果
-
Promise的参数是一个回调函数(同步),返回值为仍Promise对象
-
Promise对象必须实现then方法,而且then必须返回一个Promise对象,同一个Promise对象的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
-
then方法接受两个参数,第一个参数是成功时的回调(异步),在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调(异步)
-
-
Generator函数
-
async/await
-
async是用来声明一个异步方法,await用来等待异步方法的执行
-
正常情况下await后边是一个Promise对象,返回该对象的结果。如果不是Promise对象,直接返回封装好的promise。await都会阻塞函数中后边的代码(即加入微队列)
-
js执行机制:
-
首先判断js代码是同步还是异步,同步就进入主线程,异步就进入event table
-
异步任务在event table中注册函数,当满足触发条件后,被推入event queue
-
同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中
宏任务&微任务
-
宏任务:setTimeout、setInterval、I/O...
-
微任务:promise
-
promise:异步的容器
-
三种状态:pending fullilled rejected 具有固化性 变为成功就不会在执行rejected函数
-
同步代码优先执行,再执行异步代码, promise内部的为同步 只有then中的成功和失败函数才是异步任务。异步任务放在异步队列中。微任务与宏任务相比 微任务先执行
-
作用域和执行上下文
- 作用域是一个很抽象的概念,类似于一个“地盘”。作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突
- 函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域,if和for没有块级作用域(let和const可以形成),只有函数作用域。
- 一共有三种方式可以形成作用域,全局、函数、块。全局作用域在运行时便默认存在,我们可以使用的是函数与块作用域。
- 作用域链:
- 现在当前作用域查找a,如果有则获取并结束。如果没有则继续;
- 如果当前作用域是全局作用域,则证明a未定义,结束;否则继续
- (不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
- 跳转到第一步。
- 执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段。
- 创建阶段:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。
- 执行阶段
- 回收阶段:执行上下文出栈等待虚拟机回收执行上下文
var a = 10;
function fn(){
console.log(a)//10 函数创建时就确定了a要取值的作用域
}
function bar(f){
var a = 20;
f();
}
bar(fn)
- 全局代码的上下文环境数据内容为
| A | B |
|---|---|
| 普通变量、函数表达式,如: var a = 10; | 默认赋值为undefined |
| 函数声明,如: function fn() { } | 赋值 |
| this | 赋值 |
如果代码段是函数体:
| A | B |
|---|---|
| 参数 | 赋值 |
| arguments | 赋值 |
| 自由变量的取值作用域 | 赋值 |
- 执行上下文栈 : 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境,并压入栈的顶部。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程。
this
- 默认绑定规则:函数独立调用 指向window 与函数位置没有关系 立即执行函数也指向window
var obj = {
a: 2,
foo: function () {
var a = 3;
console.log(this);//obj
function test() {
console.log(this);//window
console.log(a);//3
}
test()
}
}
obj.foo()//隐式绑定规则
-
隐式绑定绑定规则:谁调用指向谁
隐式丢失:
-
变量赋值
function foo() { console.log(this); } var obj = { a: 2, foo: foo } var bar = obj.foo; bar();//window -
参数赋值
父函数有能力决定子函数的this
var arr = []; arr.forEach(function (item, index, arr) { console.log(this);//window }) arr.sort(function (a, b) { console.log(this);//window return a - b; }) setInterval(function () { console.log(this);//window })
-
-
显示绑定规则:
-
apply
传参:
bar.apply(obj,[ 1 , 2 , 3 ]) -
bind
传参:
bar.bind(obj)(1 , 2 , 3 ) -
call
传参:
bar.call(obj , 1 , 2 , 3) -
obj为1时,this绑定则为Number 而null和undefined没有包装类,this绑定为window
-
-
new绑定:绑定为实例化对象
-
优先级:new>显示绑定>隐式绑定>默认绑定
-
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this 。
闭包
-
上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放
-
闭包的情况
-
函数作为返回值
-
函数作为参数被传递
-
作用域内部变量被外部环境变量所接受
var c = 1; function fn() { let a = 10; console.log(c); c = function () { console.log(a); } } fn() c()//10 console.log(a)//a is not defined
-
-
闭包的作用
-
保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其它区域中的变量不会有任何的冲突(防止全局变量污染)
-
保存:如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文中调取使用(延伸变量的作用范围)
-
-
危害
- 内存泄漏:闭包产生的变量都会常驻在内存中,对内存的消耗非常大,如果滥用闭包,会造成内存泄漏,降低网页的性能;