序
温故而知新,可以为师矣。
首先先进行一个自我介绍,笔者为20届本科毕业的一名小前端,现在就职于杭州某公司。经验不算短也不算长,但是最近进入了一个职业迷茫区,不知道明天该干什么,对未来的职业规划也是有点混乱。所以在此迷茫之际,笔者选择把之前的所掌握的职业技能在重新温习一遍,看看是否能有一番不同的收获。
原型链和原型
prototype
对于学习原型链首先要知道prototype属性,这个属性是js中专属于函数的属性,他指向的是函数中原型。而原型是每个js对象(null除外)在创建时候会与之关联一个对象,这个对象就是原型,每个一个对象都会从原型中继承属性。
function Eagle(){}
Eagle.prototype.name='eagle'
let eagleObject=new Eagle()
console.log(eagleObject.name)
//输出 eagle
__proto__(注意两个下划线)
看了上面的实例相信一些初学者会有一些迷惑,构造出来的实例为什么没有定义name属性也可以输出name?构造出来的对象和构造函数的实例和构造函数的prototype的关系又是什么样的呢?。
这就是涉及到了另一个属性__proto__,但是需要注意_proto_属性是对象专有的属性他指向的是构造函数的prototype属性。
function Eagle(){}
Eagle.prototype.name='eagle'
let eagleObject=new Eagle()
console.log(eagleObject.__proto__ === Eagle.prototype)
//输出 true
constructor
既然构造出来的实例可以指向原型那么原型是不是也可以指向实例呢?答案是不能的因为实例能构造出来多个实例,原型和实例是1:n的关系,但是原型指向构造函数是可以的,通过constructor每个原型都有一个construtor属性指向关联的构造函数。
原型链
js原型链是一个耳熟能详的叫法,他实际上是一个查找的方向。当某个实例化对象需要使用某个属性,如果他自身上没定义这个属性,js会通过__proto__去这个对象上的原型上进行查找,若原型上也未查找到,则通过原型的原型进行查找,直至查找到Object的原型为止(Object的原型为null,因为Object为最顶层,js中万物皆对象,函数实质上也是对象的一种)
原型链查找流程图:
原型链继承
因为在js设计初期,并没有class的概念所以也不存在继承关系,但是通过js的特性实现继承,但是方法有很多种,原型链连继承就是其中一种,其他的种类有空单独开一个文章进行讲解
//代码实现
function Eagle(){}
Eagle.prototype.name='eagle'
function Bird(){}
Bird.prototype=new Eagle()
Bird.prototype.constructor=Bird
let birdObject=new Bird()
console.log(birdObject.name)
//输出 eagle
原型链继承缺点:1.多个原型指向同一个实例,当有多个实例化子对象时,其中一个修改原型上的属性,其他实例也会被影响,数据不是相对独立的会造成数据污染。2.无法实现super功能,无法对父类进行传递参数。
词法作用域和动态作用域
作用域一般来讲指的是源代码中定义变量的区域,在代码运行中给使用的变量提供一个查找方向和访问权限。一般来讲一门语言的作用域性质在他设计之初就已经敲定了比如js采用的就是词法作用于,也就是静态作用域。
静态作用域和动态作用域
静态作用域
静态作用域是指在函数定义时作用域就已经确定了不会随着他的调用而更改。
let text=1
function foo(){
console.log(text)
}
function foo2(){
let text=2
foo()
}
foo2()
//输出为1
动态作用域
顾名思义作用域会随着他的调用对象不同,指向的作用域不同。典型的语言是bash语言
作用域链
作用域链也是和原型链一样是一个抽象的查找方向,当你在作用域中使用一个变量编译器会在当前作用于中进行查找概变量是否被定义如果被定义就直接使用,如果未找到就去上层作用域进行查找,如此反复直至查找到全域作用域,若还未找到就直接报错该变量未被定义
执行上下文
当js代码执行一段可执行的代码时,会先读取这段代码中所有可执行的类型,可执行类型包含是那种全局代码,函数代码,eval(内置函数)代码
执行上下文栈
在代码进行分析阶段时js引擎会先创建一个执行上下文的栈,将读取到的可执行代码按照顺序压到栈中,执行之后进行释放。
//用数组模拟一下上下文栈
let ECStack=[]
ECStack=[globalContext]
function fun1(){
console.log(1)
}
function fun2(){
fun1()
}
function fun3(){
fun2()
}
fun3()
//处理顺序
//fun3()调用
ECStack.push(<fun3>)
//执行fun3时发fun2也被调用
ECStack.push(<fun2>)
//执行fun2时发fun1也被调用
ECStack.push(<fun1>)
//fun1执行完毕弹出
ECStack.pop()
//fun2执行完毕弹出
ECStack.pop()
//fun3执行完毕弹出
ECStack.pop()
例子相同的输出结果但是执行顺序不同
function foo(){
console.log(1)
function foo2(){
return 2
}
return foo2()
}
console.log(foo())
//2
//执行顺序
ECStack.push(<foo>)
ECStack.push(<foo2>)
//弹出foo2
ECStack.pop()
// 弹出foo
ECStack.pop()
-------------------------------------
function foo(){
console.log(1)
function foo2(){
return 2
}
return foo2
}
console.log(foo()())
//2
//执行顺序
ECStack.push(<foo>)
// 弹出foo
ECStack.pop()
ECStack.push(<foo2>)
//弹出foo2
ECStack.pop()
变量对象
变量类型
js在相关数据作用域中定义许许多多个变量,而在js中变量类型基本上分为两大类基础数据类型和复杂数据类型。
基础数据类型
基础数据类型分为:String、number、Boolean、undefined、null,这些变量存储在栈中,在使用时直接进行使用,复制时也直接将值复制一份和原本数据相对独立
复杂数据类型
复杂类型又叫做引用类型,一般是指Object类型,包括Array,Data。这些类型的变量存放在堆中,一般使用只是引用了堆中的某个地址,当被复制是复制的也是对象的堆地址,如果堆存储的地址被改变所有引用的地方值也会被改变,数据相互不独立。
let bar={
value:1
}
console.log(bar.value)
//1
let barTwo=bar
barTwo.value=2
console.log(bar.value)
//2
深拷贝和浅拷贝
在js中默认的所拷贝都是浅拷贝,浅拷贝对于基础数据类型会将其对应的值直接拷贝一份,拷贝完的基础数据类型和原本的相互独立,但是对于对象的复制只是复制的引用地址,数据不相对独立会被相互污染。所以就有了深拷贝的诞生。
深拷贝可以做到讲复杂数据类型中的数据完全拷贝出来并且相互独立只不过需要自己去手写,实现逻辑多半为递归
let deeoClone = (data) => {
//递归出口
if (!data || !(data instanceof Object) || typeof data == 'function') {
return data || undefined
}
let result = Array.isArray(data)?[]:{}
for (var key in data) {
if (data.hasOwnProperty(key)) {
result[key] = deeoClone(data[key])
}
}
return result
}
变量提升
变量提升即将变量声明提升到它所在作用域的最开始的部分,但是赋值操作位置不变。
console.log(a)
//不会报错,会输出undefined
var a=1
console.log(a)
//1
函数提升
js在执行之前,会把foo函数提升到最前面,所以我们在fun函数定义之前就可以使用fun函数而不会报错。
fun1()
//1
function fun1(){
console.log(1)
}
而且函数的变量提升优先级高于变量提升
var foo=function() {
console.log(1)
}
foo()
//1
var foo=function() {
console.log(2)
}
foo()
//2
function foo(){
console.log(3)
}
foo()
//2
function foo(){
console.log(4)
}
foo()
//2
//由于函数变量提升导致第三和第四定义的函数会优先提升声明导致定义先生效,
//第三次函数定义被第四次函数覆盖,其次定义声明才轮到第一和第二次变量声明,
//当js开始执行是走到第一次函数赋值操作覆盖了第四次函数定义,输出为1,
//执行到第二次函数赋值覆盖了第一次赋值输出为2,后续执行没有覆盖行为,全部调用时输出为2
结尾
经过一段时间的努力本次js基础的分享到此结束,笔者没想到分享文章比预想的要费力很多,可能也是平时做知识输出过少,有些不足的地方希望大家海涵。若有宝贵的建议也请在评论区提出,笔者将一一采纳,感激不尽。下次将要分享js中另一个比较重要的知识点关键字this敬请期待。