1、原型与原型链
每个函数都有一个prototype属性,默认指向一个空的Object对象(原型对象)
原型对象有一个属性constructor,指向函数对象。
//原型对象中有一个constructor 对象指向函数对象
consoloe.log(Data.protype.constructor === Data)
//给原型对象添加属性(方法)一般是给实例对象使用的
function Fun(){
}
Fun.protype.test = function(){ console.log('tianze')}
let fun = new Fun()
fun.test();//tianze
显示原型与隐式原型
1、函数都有一个prototype 属性(显示原型)=>定义函数时自动添加,默认为空的Object对象
2、实例对象都有一个__porto__属性(隐示原型)=>创建对象时自动添加,默认为构造函数的prototype属性
3、对象的隐式原型值对应其构造函数的显示原型的值
function Fun(){} //内部执行语句=> Fun.prototype = {}
let fn = new Fun();//内部执行语=> this.__proto__ = Fun.prototype
console.log(fn)
console.log(Fun.prototype===fn.__proto__) //true
原型链
1、访问对象的属性时,先在自身属性查找,找到返回
未找到就沿着__proto__这条链查找找到返回,如果没有找到返回undefined
(待补充......)
作用域链
1、作用域:
(1)所有末定义直接赋值的变量自动声明为拥有全局作用域
(2)所有window对象的属性拥有全局作用域(全局访问)
(3)在一个js文件可能会写好多个js模块,一些模块没有进行块级作用域操作就会污染全局变量
书写方式;(function(){//js模块})();
2、函数作用域
(1)指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部。
(2)内层作用域可以访问外层作用域,反之则不行
(3)块语句(大括号“{}”中间的语句),如 if 和 switch 条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域
3、块级作用域
(1)let和const声明,所声明的变量在指定块的作用域外无法被访问.一个{}就是一块(太爽了)
(2)let /const 声明变量不会提升到代码块顶部(先声明在使用、不能重复命名=>用得都舒服多了)
(3)重点就是for循环得变量设置
4、作用域链
//自由变量:当前作用域没有定义此变量,但是对此变量进行操作了
//作用域链:当前作用域没有就会向上一层找
var a = 100
function F1() {
var b = 200
function F2() {
var c = 300
console.log(a) // 自由变量,顺作用域链向父作用域找
console.log(b) // 自由变量,顺作用域链向父作用域找
console.log(c) // 本作用域的变量
}
F2()
}
F1()
function fn() {
console.log(x)
}
function show(f) {
var x = 20
(function() {
f() //10,而不是20
})()
}
show(fn)
个人理解:
函数的作用域,在定义时就已经确定了,而不是在再里层得作用域一层一层得查找。
他的理解:
要到创建这个函数的那个域”。 作用域中取值,这里强调的是“创建”,
而不是“调用”,切记切记——其实这就是所谓的"静态作用域"
var a = 10
function fn() {
var b = 20
function bar() {
console.log(a + b) //30
}
return bar
}
var x = fn(),
b = 200
x() //bar()
fn()返回的是bar函数,赋值给x。执行x(),即执行bar函数代码。取b的值时,
直接在fn作用域取出。取a的值时,试图在fn作用域取,但是取不到,
只能转向创建fn的那个作用域中去查找,结果找到了,所以最后的结果是30
探索instanceof
A instanceof B 是如何判断的A 是 B 的三实例对象
如果B函数的显示原型对象在A对象的原型链上,就返回true 否则就返回 false
实例对象的隐式原型指向函数的显示原型
console.log(Object instanceof Function)//true
console.log(Object instanceof Object)//true
console.log(Function instanceof Funtion)//true let fun = new Function()
console.log(Function instanceof Object)//true
function Fun(){}
console.log(Object instanceof Foo) //false
原型面试题
var A = function(){
}
a.prototype.n = 1
var b = new A()
A.prototype = {
n:2
m:3
}
var c = new A()
conssole.log(b.n,b.m,c.n,c.m) //1 ,undefined,2,3
var F = function(){}
Object.prototype.b = function(){
console.log('a()')
}
Function.prototype.b = function(){
console.log('a()')
}
var f = new F()
f.a()
f.b() //b unedfined
F.a()
F.b()
执行上下文与执行上下文栈
变量声名提升,在定义语句之前可以访问到
值:undefined
函数声明提升:(不是很细节)
通过function声明的函数,在之前可以调用
值:函数定义(对象)
字面量定义的函数,不存在函数声明提升。
console.log(a1,window.a1)
window.a2();
console.log(this)
var a1 = 3
function a2(){}
函数执行上下文:
在调用函数,准备执行函数体之前,创建对相应函数执行上下文对象
对局部数据进行处理
形参变量==>赋值(实参)==>添加执行上下文属性
arguments==>赋值(实参列表),添加为执行上下文属性
var 定义的局部变量==>undefined,添加为执行上下文属性
function 声明的函数 ==>赋值(fun),添加为执行上下文的方法
this==>赋值(调用函数的对象)
开始执行代码
function fn (a1){
console.log(a1)//2
console.log(a2)//undefined
a3() //a()
console.log(this) //window
console.log(arguments) //伪数组
var a = 3
function a3(){
console.log('a3()')
}
}
fn(a,3)
函数的执行上下文是在自己的作用域内开辟的一块(栈)空间
执行上下文栈
//1.进入全局上下文
var a = 10
var bar = function(x){
var b = 5
foo( x + b ) //3、进入foo执行上下文
}
var foo = function(y){
var c = 5
console.log(a+c+y)
}
bar(8) //2、进入bar函数上下文
函数执行完毕就会释放当前的内存空间,(出栈)
最终保留的就是window的执行
执行上下文题目:
console.log('gb:'+i)
var i = 1
foo(1)
function foo(i){
if(i == 4)
{
return
}
console.log('fb:'+i)
foo(i+1)
console.log('fe:'+i)
}
console.log('ge'+i)
//结果输出:1,2,3,3,2,1, 1
//递归调用的,未完当前函数操作继续执行该函数,当递归条件不支持时,就会执行出栈操作
//整个过程产生 5 个执行上下文 (funtion)
var c =1
function c(c){
console.log(c)
}
c(2) //报错 =>var c = 1存在变量提升访问c时 c是一个变量
function a(){}
var a
console.log(typeof a)//'undefined'
//先执行变量提升,在执行函数提升
//实际代码的执行顺序
var a
function a(){}
执行上下文和代码的执行是俩快区域,当在执行代码块访问变量时,
先去执行上下文查看是,第一个名为 a的是一个 var a所以执行返回的是 'undefined'
if(!(b in window)){ //b in window => window中存在 b这个属性嘛?
var b = 1
}
console.log(b) //undefined
参考文章: