原型
构造函数:使用构造函数创建对象时调用的函数(也就是跟在new后面的函数),通常首字母大写。
prototype属性:仅存在于构造函数中,是构造函数身上的一个属性,该属性是一个对象,这个对象也就是所谓的原型对象,这个对象指向调用该构造函数创建的实例对象的原型(即xxx.__proto__的访问的这个对象,也就是所谓的是实例的原型对象)
⚠️ __proto__属性是可以访问到的,指向实例的原型对象,只是实例对象身上看不到该属性,也就是所谓的隐式原型
[[prototype]]:在主流浏览器中就是__proto__属性,它是无法直接通过实例.[[prototype]]去访问的,只能通过Object.getPrototypeOf来访问。
prototype和__proto__的区别: prototype是由构造函数创建的原型,是一个对象,也就是构造函数创建的实例的原型对象。__proto__是实例对象身上的一个属性,它指向的是实例的原型对象,是一个引向。
构造函数:
构造函数的prototype:
实例(由图中可知,实例身上没有constructor属性):
实例的__proto__属性(指向实例的原型,所以图中的打印一摸一样,本质上指向的都是是堆中同一个对象)
对象的原型:Object.getPrototypeOf() 静态方法返回指定对象的原型(即内部 [[Prototype]] 属性的值
由上图可知:
1、实例的__proto__和构造函数的prototype属性指向是相同的,指向实例的原型对象:person.__proto__ === Person.prototype
2、constructor属性仅存于实例的原型对象身上,即Person.prototype.constructor === Person
3、所有的对象都是由new Object创建,即实例的原型对象是由new Object创建,由此得出实例的原型对象身上的__proto__属性指向Object的原型。
关系网:其中红色虚线就是原型链
思考: 为什么person.constrctor === Person是true?
由上述可知: constructor属性仅存在与实例的原型对象上,实例身上是没有这个属性的,所以根据原型链查找规则,去实例的原型对象身上找,Person.prototype身上是存在constructor这个属性的,因此返回true
继承: 每一个对象都会从原型链中继承属性,在js中对象是一个引用类型,所以继承并不是意味中赋值也不是复制了一份相同的代码,而是将指针复制了一份,两个指针指向堆中同一个对象
词法作用域and动态作用域
作用域
概念:定义变量的区间,能够确定变量的可访问范围
意义:确定了当前代码执行的时候哪些变量可以访问,是一个标准
分类:
静态作用域:在函数定义的时候就已经确定了,跟在哪里调用没有任何关系,一旦函数声明完成,可访问的范围立即确定。(在js中采用的是静态作用域,也就是词法作用域)
动态作用域:由函数调用时决定
// case 1
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
// case 2
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
答案:local local
注:都是在checkscope函数内部创建的f函数,去访问scope会先从checkscop函数内部查找,若内部没有这个变量不存在,则回去全局作用域中查找。
作用域如图所示:
总结:作用域是在声明的时候决定的,this的指向是由调用方式决定的
执行上下文
概念:是一个抽象概念,可以理解为创造了一个环境,所有的js代码执行都必须在这个环境里面。
//变量声明提升
//首先返回的是一个变量,是一个函数表达式
var foo = function(){
console.log("foo1")
}
foo() //foo1
var foo = function(){
console.log("foo2")
}
foo() //foo2
//函数声明提升
function foo(){
console.log("foo1")
}
foo() //foo2
function foo(){
console.log("foo2")
}
foo() //foo2
console.log(add1(1,2)) //3
function add1(a,b){
return a+b
}
console.log(add2(1,3)) //报错:add2 is not a function
//变量声明提升:只提升变量,函数留在当前位置,直到js执行到这一行才开始进行赋值操作
var add2 = function(a,b){
return a+b
}
//思考:此时两个console.log有什么区别?
//知识点:函数声明提升,当我们使用函数声明一个时,会将函数放到当前执行代码的前面,当使用变量声明函数时候,会出现变量声明提升,此时var add 2 = undefined,此时undefined不是一个函数出现上述错误
栈(stack):是真实物理空间上的一个栈,这个栈就以为着是一个执行上下文的栈(execution context stack 简称 ECS),用来管理执行上下文的。
栈的特点:先进后出
执行过程:当进入到js代码的时候时候会先创建一个全局上下文栈,在函数执行的时候再创建一个函数执行上下文栈,这个栈的底部永远存放的的全局上下文,所有的函数全部执行完,全局上下文栈出栈。
如何判断上下文何时出栈:
看返回的是函数的调用还是函数,若内部仍存在函数调用,则需要等到内部所有函数调用完成之后才能进行出栈操作,若内部返回的时候变量或者函数,则立马出栈。
如图说明有什么区别?
区别
- checkscope():先全局执行上下文进栈,后执行 checkscope()执行上下文进栈,内部出现 return fn()函数执行,fn()执行上下文进栈道,出栈道顺序:fn()-->checkscope()-->全局执行栈
- checkscope()():先全局执行上下文进栈,后执行checkscope()执行上下文进栈,只是返回了一个变量,内部没有其他函数依赖checkscope执行环境,checkscope()出栈,之后fn()开始进栈道。出栈顺序:checkscope()-->fn() -->全局执行栈
思考:为什么checkscope()的执行上下文都已经销毁了,执行fn()得到的scope的答案已经是local scope而不是global scope?
在创建一段可执行代码时会做什么是事情呢?
三大属性:
1、变量对象
概念: 创建变量的一个作用域,存放执行上下文中声明的一些变量或者函数
分类:
全局变量对象(VO):js提前预制好的变量对象(例如:parseInt、Math.ceil等方法),可以使用window直接去使用全局变量
console.log(Math.random())
console.log(this) //在global环境中this默认指向作用域链的最顶端,也就是window
函数上下文的变量对象(AO):函数调用的时候才会执行,也就是激活的时候创建的对象(active Objct),简称AO,对应活动态的一个对象,也就是活动对象。
AO和VO的区别:AO是存放函数执行上下文的变量声明、函数声明以及arguments,而VO是放全局执行上下文的变量声明,函数声明以及argumengts
当进入执行上下文中,此时还没执行函数,在准备中此时js引擎针对变量对象做了三件事: (1)函数声明 (function(){} 这也是为什么我们在函数声明前执行函数是ok的原因,在执行前就已经将函数进行了声明) (2)变量声明(只会去声明变量,赋值是在代码执行中去做,此时值为undefined) (3)声明arguments
代码执行之后(代码执行是按照顺序执行的):此时依次开始赋值
误区知识点: 声明提升
- 函数变量提升:使用函数声明的函数才具有声明提升,使用函数表达式的函数不具备此功能
- 变量提升:使用var声明的变量会将变量提升到作用域的最顶端
console.log("foo",foo) //输出foo函数
console.log("foo",foo) //输出foo函数
//原因:函数表达式不具备声明提升,所以访问foo的时候都访问的都是foo函数
function foo() { console.log('foo1'); }
var foo = function() { console.log('foo2'); }
console.log("foo",foo) //输出foo2函数
console.log("foo",foo) //输出foo2函数
//原因:当变量和函数名相同时候,会发生覆盖问题,谁在后面就输出谁
注意:在函数内部没有使用var、let、const去声明的变量,此时意味着这个变量是不是存储到函数上下文中,在函数执行的时候,才将他在全局执行上下文中声明,在声明前访问都是无法找到这个变量的
function foo() {
console.log('a', a);
a = 3;
}
foo(); //报错,无法找到a
// AO = {
// argumengs: {
// length: 0
// }
// };
function bar() {
a = 1;
console.log('a', a);
}
bar(); //1
// AO = {
// argumengs: {
// length: 0
// }
// };
// VO = {
// a: 1
// };
2、作用域链
概念:查找变量的规则,查找变量时,首先会从当前的执行上下文中查找,若在当前上下文中未找到会去父级的执行上下文中去查找,按照层级一直往上找,直到全局上下文也就是window对象,若还未找到,则该变量不存在。
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
创建到执行:
1、首先创建函数,保存作用域链到内部属性 [[scope]] (通过这个属性来表达作用域的意思)
checkscope.[[scope]] =[
//最外层的作用域一定是全局上下文变量对象
globalContext.VO
]
2、执行函数,创建函数执行上下文,并且将执行上下文压入执行上下文栈中
ECStack = [
checkscopeContext,
globalContext
];
3、准备执行上下文的三要素,
(1)保存作用域链,需要创建将作用域链赋给上下文的scope属性
checkscopeContext={
scope:checkscope.[[scope]]
}
(2)初始化活动对象
AO={
argments:{
length:0
},
scope2:undefined,
scope:checkscope.[[scope]]
}
4、将活动对象压入checkscope作用域链的最顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
5、执行函数,修改AO中的属性值
AO={
argments:{
> length:0
},
scope2:'local scope',
scope:[AO,[[scope]]]
}
6、查找到scope2的值,函数执行完毕,函数上下文从执行上下文栈中弹出,函数上下文环境销毁。
3、this
核心:谁调用的当前这个值,则this就指向的是谁
闭包
MDN:能够访问自由变量的函数
自由变量:即不是函数的参数,也不是函数局部变量的变量(不是函数内部的值,但是在函数内部能够使用)
var a = 1
function foo(){
console.log(a)
}
foo() //1
本质:获取到作用域链中上一层的变量对象(切记js中是静态作用域!!!!!)
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();