JS高级小知识

JS线程:

线程是操作系统的最小单位,一个进程包含多个线程。
主线程负责渲染(GUI)执行JS

浏览器的EventLoop分为:
一个函数执行栈、一个事件队列和一个任务队列。

单线程的含义

浏览器是multi-process(多进程)的,一个浏览器只有一个Browser Process(浏览器进程) ,负责管理Tabs、协调其它process(进程)和Renderer process(渲染器进程)存至memory内的Bitmap绘制到页面上的(pixel);==在Chrome中,一个Tab对应一个Renderer Process,Renderer process是multi-thread(多线程),其中main thread(主线程)负责页面渲染(GUI render engine) 执行JS(JS engine) 和 event loop; network component 可以开2~6个 I/O threads平行去处理

一个函数调用栈, 一个task队列, 一个微任务队列, 在eventloop调度一个宏任务之前,先查看微任务队列是否有未执行的任务,如果有,先执行完所有的微任务。
一轮事件循环的耗时是不确定的
NdeJS中有多个宏任务队列

下面我们来看一段代码深入理解一下

console.time("start")

setTimeout(function () {
    console.log(2);
}, 10);

new Promise(function (resolve) {
    console.log(3);
    resolve();
    console.log(4);
}).then(function () {
    console.log(5);
    console.timeEnd("start")
});
console.log(6);
console.log(8);
requestAnimationFrame(() => console.log(9))

3, 4, 6, 8 , 5 start(第一轮eventloop结束 -> 执行时间), 9 2

更多的关于Node和浏览器的线程问题请访问我的另一个博客:传送门

执行上下文

每个上下文都有变量对象,只有调用了函数后,才会产生上下文,

当调用一个函数时, 函数内部的变量对象 称为 活动变量对象,虽然 函数内部 也是 有 变量对象的, 但是我们称为活动变量对象, 和 全局的上下文变量 不是同一个

变量对象 = 全局变量+全局函数声明 = 全局上下文变量对象
活动对象 = 局部变量+形参+arguments+局部函数声明 = 函数上下文变量对象
例子1:

if (!("a" in window)) {
    var a = 1;
}
alert(a);

分析:首先这些都是在全局VO下面的,所以,在执行上下文的两个阶段中可以这样看:
进入执行上下文阶段:
VO(global) = {
a:undefined
}
执行代码阶段:
执行的时候首先碰见的是 if 判断语句,判断a是否存在于window当中,在进入执行上下文阶段的时候,我们可以看到已经声明了a, 所以判断为错误的, var a = 1,不执行

例子2:

function a(x){
	return x * 2
}
var a;
alert(a)			// 函数a

分析:为什是函数a呢? ,因为在进入执行上下文阶段,出现了同样的名字,一个是函数声明,一个是变量声明,但是在JS当中VO(变量对象)的填充(提升)顺序为:函数形参 -> 函数声明 -> 变量声明。
所以在这个例子中,我们的函数a 是优先于变量a 提升到执行栈的,
例子3:

alert(a)			// 函数a
var a = 1;
function a(x){
	return x * 2
}

分析: 在进入执行上下文的时候,首先a是全局变量对象中的变量,所以会事先声明
进入执行上下文阶段:
VO(global) = {
a:undefined
}
执行代码阶段:
a被赋值为1,但是由于下方声明了一个函数a,所以根据VO(变量对象)的填充(提升)的顺序,函数声明大于变量声明,在这里会优先提升函数a,所以输出函数a,但是如果将alert(a)放在最后面 就是 输出1 了 因为这里是正常的JS顺序,a则代表变量

作用域链

作用域链是一个把所有父(函数)变量对象__加上(在作用域链头部的)函数自身变量/活动对象的一个列表。但是,这个作用域链也可以包含任何其他对象,比如,在上下文执行过程中动态加入到作用域链中的对象 -> 像with对象或者特殊的catch从句对象,当前执行上下文销毁,作用域链也会销毁

静态作用域和动态作用域

简介:JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置
静态作用域:当一个函数被创建出来的时候,就已经确定了它 的作用域

var x = 10;
function foo(){
	console.log(x);
}
(function(funArg){
	var x = 20;
	funArg()
})(foo)

拿上方这个例子来说,当创建foo()函数的时候就已经确定了它的作用域范围,在函数内部输出x,首先会去寻找函数内部是否有这个x,如果没有,就会顺着作用域链 去寻找外面有没有这个x,如果存在就输出, 如果不存在就报错,最后执行自调用函数
在这里插入图片描述

动态作用域:是取决于谁调用了这个函数,还是拿上方这个例子来说:在自调用函数中调用了foo()后,foo()函数内部输出x,他会首先去寻找自身函数内部是否有这个x,如果没有,就会去寻找是谁调用的它,在这个例子中,是自调用函数调用的foo() 所以,能够找到x = 20;所以foo中会输出20,而不是一开始在全局声明的10
JS中是没有动态作用域这么一说法的, 但是我们是可以利用this来形成伪动态作用域,还记得在描述动态作用域时,我们说的:动态作用域是取决于谁调用的它,所以我们可以利用this来指明它的作用域在哪里,如下方这个例子

var x = 10;
function foo(){
	console.log(x);
}
(function(funArg){
	this.x = 20;
	funArg()
})(foo)

在该代码中,我们可以看到在自调用函数中,我们利用this指定了x的值, 而这里的这个this指的是Window对象,也就是说,this.x = 20 是 建立在 全局的变量上的, 也就是能够替换掉事先声明的var x = 10; 达到一种伪动态作用域的情况

总结:静态作用域和动态作用域的区别
静态作用域是定义时就已经确定好作用那些变量。
动态作用域是取决于谁调用了,就能够作用哪些变量

活动对象(函数执行上下文变量对象)

function fo(x, y){
	alert(foo.length)							// 函数形参个数
	alert(arguments.length)						// 函数的实参个数
	alert(arguments[0/1])						// 输出函数参数的值
	alert( x === arguments[0])					// 參數共享屬性
	alert(arguments.callee === foo) 			// arguments.callee 实际调用的是自身 
}

闭包

面试标准答案:闭包是一个代码块和所有父作用域的一个集合体。

内部函数在被创建的时候会在它的[[Scope]]属性中保存父函数的作用域链,所以当函数被调用的时候,它的上下文作用域链会被格式化成活动对象与[[Scope]]属性的和

作用:模拟块级作用域,私有化变量,避免被垃圾回收机制回收, 创建模块

var nAdd;
var t = function() {
    var n = 99;
    nAdd = function() {
    	 n++;
    }
    var t2 = function() {
    	console.log(n)
    }
    return t2;
};

var a1 = t();
var a2 = t();

nAdd();

a1(); //99
a2(); //100

解释:关键在于nAdd(); 我们在执行var a1 = t()的时候, nAdd 被赋予了一个值function(){n++}; 我们暂且称为fn1, 接着当我们执行var a2 = t()的时候,nAdd又被赋予了一个新的值,那么这个新的值称为fn2; 当我们调用nAdd()的时候,其实是相当于调用了fn2(),而不是fn1(),我们更改的其实是a2形成的闭包里的n的值,并没有更改a1形成的闭包里的n的值,

This

this是一个执行上下文的特殊对象。因此,它可以叫做上下文对象(也就是用来执行执行上下文是哪个上下文中被触发的对象)。
1.任何对象都可以作为上下文中的this值
2.this是执行上下文的一个属性,而不是变量对象的一个属性。
3.在代码中访问this的说,它的值是直接从执行上下文中获取的,并不需要任何作用域链查找,this的值只在进入执行上下文的时候进行一次确定
4.this值是如何在不同的调用中(函数触发的不同的方式),由caller给出不同的结果

如何确定this的值?
影响调用上下文找那个的this值的只有可能是调用表达式的形式,也就是调用函数的方式。 用一句话来说明:谁调用了它,this就指向谁

function Person(age){
	console.log(this)
	this.age = age
	return age
}
var instance = new Person(12)				// this指向new出来的Person

this的指向,在new出来一个函数的时候,指向这个函数方法的本身

手写Call(Call的原理)

我们所知道,call是用来改变this的指向,我们先来看个例子

var my_str = {
	name:'张三'
}
function show(){
	console.log(this.name)
}
show()
show.call(my_str)


在这里插入图片描述
这是一个极其简单的call的运用,从这里我们可以推导一下,调用call方法的时候,改变了this指向了my_Str,随后又在my_Str中调用了show方法,由这个推到我们可以写出以下代码:

var my_Str = {
	name:'张三',
	show:function(){
		console.log(this.name)
	}
}
my_Str.show()

运行之后发现是成功的,那么我们就可以根据代码来推到一下整个call的流程:

  1. 将函数设为了该对象的属性
  2. 执行了该函数
  3. 删除该函数(这个函数是使用的时候产生的,不能一直存在的)

接下来我们来用代码来实现这3个步骤
第一版代码:

Function.prototype.myCall = function(context){
	console.log(context)
	console.log(this)
	// 首先获取调用myCall的函数,用this来确定
	context.fn = this			// 将该函数赋给对象中fn属性
	context.fn();					// 执行该属性
	delete context.fn			// 删除该属性
}
var my_Str = {
	name:'李四'
}
function show(){
	console.log(this.name)
}
show.myCall(my_Str)

在这里插入图片描述
目前虽然实现了call的功能,但是我们JS的call是可以传参数的,当我们不确定有多少个参数的时候,所以我们可以利用ES6再对代码进行一番修改

Function.prototype.myCall = function(context, ...args){
	// 首先获取调用myCall的函数,用this来确定
	context.fn = this			// 将该函数赋给对象中fn属性
	context.fn(args);					// 执行该属性
	delete context.fn			// 删除该属性
}
var my_Str = {
	name:'李四'
}
function show(...args){
	console.log(this.name)
	console.log(args)
}
show.myCall(my_Str, '张三', '王五')

在这里插入图片描述

手写apply

原理和call是一样的

// JS自带的apply:
// 
// var obj = {name:'李明'};
// 
// function show(){
// 	console.log(this.name)
// }
// 
// show.apply(obj)


// 手写apply
var obj = {name:'李明'};

function show(...args){
	console.log(args)
	console.log(this.name)
}

Function.prototype.my_Apply = function(ctx, ...args){
	ctx.fn = this;
	ctx.fn(args);
	delete ctx.fn;
}

show.my_Apply(obj, '1', '2')

手写bind

// JS自带的bind:
// bind的特性: 1.返回一个函数 2、可以传参数

// var obj = {name:'李明'};
// 
// function show(){
// 	console.log(this.name)
// }
// 
// var newFn = show.bind(obj);
// newFn()


// 手写

var obj = {
	name:'李明'
};

function show(){
	console.log(this.name)
}


Function.prototype.my_Bind = function(context){
	var self = this;
    return function () {
        return self.apply(context);
    }
}

var newBind = show.my_Bind(obj);
console.log(newBind())

手写New、New的原理、 New的作用

  1. New的作用
    首先我们来看一个例子:
function Fc(name, age, year){
	this.name = name;
	this.age = age;
	this.year = year;
	this.show = function(){
		console.log(`姓名:${this.name}, 年龄:${this.age}`)
	}
}
Fc.prototype.student = '第一中学';
Fc.prototype.city = '北京'

var my_fc = new Fc('张三', 19, 2019);

我们来输出一下my_fc
在这里插入图片描述
可以看出, 在经过了new之后,我们的Fc函数转为了对象,可以通过字面量直接访问到这个对象中的值,同时也可以访问这个对象上的原型的值

因为 new 是关键字,所以无法像 bind 函数一样直接覆盖,所以我们写一个函数,命名为 objectFactory,来模拟 new 的效果。
也就是下面这段代码:

function Fc(){

}
var my_new = new Fc()
		||
var my_new = objectFactory(Fc, ......)

那么我们来实际操作一下这个函数

function Fc(name, age, year){
	this.name = name;
	this.age = age;
	this.year = year;
	this.show = function(){
		console.log(`姓名:${this.name}, 年龄:${this.age}`)
	}
}
Fc.prototype.student = '第一中学';
Fc.prototype.city = '北京'

// 模拟new
function objectFactory(){
	var obj = new Object();
	Constuctor = [].shift.call(arguments);			// 得到传入的构造函数
	obj.__proto__ = Constructor.prototype
	Constructor.apply(obj, arguments);
	return obj
}
  1. 首先既然我们最后出来的是一个对象,那么我们就先声明个对象
  2. 把我们的函数刨出来,在这里我用的是这种不传参,再从arguments刨出来的方法,大家觉的麻烦可以直接传入这个参数
  3. 这个对象能够使用函数中的原型
  4. 调用这个函数同时指定函数的this为事先创建好的对象,再将参数传入。 最后也就是obj 对象中,有了这个函数(这里可以结合上面我们写的call原理来看
  5. 返回obj对象

接下来我们就可以调用这个模拟的new函数了

var my_fc = objectFactory(Fc, '张三', '18', 2018)
console.log(my_fc.name)
console.log(my_fc.year)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值