前端学习(整理)对象,原型,上下文环境,作用域,闭包等问题深入理解

版权声明: https://blog.csdn.net/zy21131437/article/details/83901256

对象

对象是属性的集合,重点:是一个集合,意思是有一个个属性集成在了一起,组成了一个集合,这个集合叫做对象,另外,这个属性是以键值对的形式存在的,键是一个字符串,比如a,比如name等,值可以是任意类型,比如数组,function,字符串,数字

----------------------------------------

prototype(原型)

每一个函数创建的时候,系统会自动给这个函数附加一个叫prototype的属性,这个prototype,我们称作为原型,而这个叫做prototype,也就是原型的属性本身是一个对象,它的本身只有一个属性就是constructor,意思是将这个原型指向这个函数,从而不会弄错父级.

虽然这个prototype只有一个自带的属性,但是我们可以给它扩展,例如:

function Aa(){}

Aa.prototype={a:1,b:2}

另外,这边还有一个__proto__,其指向的是该原型对象的prototype,这个属性是一个私有属性,是各个大浏览器厂商添加的一个属性,但是因为其广泛的使用,被大家所流传,其作用和prototype一样,也是一个原型,如果硬要说区别,大概就是显性原型(prototype)和隐性原型(__proto__)把,看个例子

function Aa(){}

Aa.prototype.__proto__ === Object.prototype //true

function AA(){}

let a=new AA(){}

a.__proto__ === AA.prototype //true

解释:

a是AA的实例对象,因此,它的__proto__指向的是其原型对象的prototype,也就是AA.prototype

Aa.prototype不仅是a的原型对象,同时自己也是一个实例对象,所以它的__proto__是Obejct.prototype

注意1:顶级的Obejct.prototype,它的__proto__是null,因为它已经是顶级了,没有原型对象

原型链

原型链,原型链,说白了就是一条链,也就是一层一层的继承下来的一条链式,例如:

function FOO(){}

FOO.prototype={}

let foo=new FOO();

这里的foo,是继承的FOO上的属性方法,而FOO除了自身prototype上的方法属性,还有从Object上继承来的方法和属性,从foo->FOO->Object,这就是一条原型链,foo不仅从FOO处继承到了属性方法,还从Object处继承到了属性和方法

注意1:如何判断是否是自己自由的属性或方法,而不是从原型链上继承到的,这里有一个方法可以判断,hasOwnProperty(),如下例

function aa(params) {}

let a=new aa();

a.name=1;

aa.prototype.name=1;

aa.prototype.age=2;

console.log(aa.prototype); //{name:1,age:2}

console.log(a); //{name:1}

for (item in a){

if(a.hasOwnProperty(item)){

console.log(item); //{name:1}

}

}

上下文环境

1.var,let,const

声明一个变量通常有三种方法,var以及在ES6中引入的let,const

var:是一种全局声明,即使声明在调用之后,也只会是显示undefined,而不会报错

console.log(a) //undefined

var a=10;

let和const:调用必须在声明之后,如果在之前,就会报错

console.log(a,b) //抛出异常错误

let a=10;

const b=10;

2.this

this,这个值在任何地方都有值,而且情况很复杂

第一种情况,当做构造函数中被调用了

function FOO(){

this.name='aa';

this.age='bb';

console.log(this) //FOO{name:'aa',age:'bb'}

}

let a=new FOO();

sonsole.log(a.name,a,age) //aa,bb

如果函数被当做构造函数使用,那么这时的this代表的是new出来的对象

但是

如果函数是被直接调用了,而不是当做构造函数使用,没有实例化,如下

function FOO(){

this.name='aa';

this.age='bb';

console.log(this) //window{...}

}

FOO()

那么,此时的的this指向的是window

第二种情况,函数作为对象的一个属性

如果函数作为对象的一个属性,并且被作为属性调用时,那么此时的this指向的是当前的这个obj

let a={

x:1,

y:function(){

console.log(this) //a{x:1,y:function()}

console.log(this.x) //1

}

}

a.y();

如果函数作为一个对象的属性,但是不是作为一个属性被调用的,那么此时的this指向的是window

let a={

x:1,

y:function(){

console.log(this) //window{}

console.log(this.x) //undefined

}

}

let ax=a.y;

ax();

第三种情况,函数使用call或者apply调用

此时的this指向的是当前传入的这个对象

let obj={x:10}

let a=function(){

console.log(this) //obj{x:10}

console.log(this.x) //10

}

a.call(obj)

第四中情况

全局和调用普通函数

全局情况下的this === window,永远等于

被当做普通函数调用时,this也永远指向window,例如

let a={

x:10,

y:function(){

function ax(){

console.log(this); //window{}

console.log(this.x); //undefined

}

ax();

}

}

3.函数,函数声明

函数也是一个全局属性即使调用在声明之后,依旧可以被使用

aa() //10

function aa(){

console.log(this) //window

return 10;

}

函数声明和函数不同,函数声明不可以在声明前调用,否则会抛出异常错误

a(); //抛出异常错误

let a=function(){

return 10;

}

另外,每次调用函数时,会产生一个新的上下文环境,并将老的上下文环境压栈(也就是降低优先级),假如这时候新的函数中有新定义的变量,且跟老的变量重名,那么仅在该函数的作用域下新的变量会覆盖老的变量直至脱离该函数的作用域,到函数调用结束,那么这个被创建的新的上下文环境会被销毁,而这个环境中的变量也会被销

自由变量

例如:

var x=10;

function(){

var b=20;

console.log(b+x) //30;

}

这里的x就是自由变量

另外:

自由变量x的取值,有人说是去父级作用域取,其实并不完全正确,正确的说法应该是,到创建的那个作用域去取,而不是调用的那个作用域,例如

var x=10;

function fn(){

console.log(x);

}

function show(f){

var x=20;

(function(){

fn//输出为10,而不是20

})()

}

show(fn)

闭包

闭包这个两个字的文字解释,还真不好说,简单的说就是函数去调用了其他作用域的变量,实际上闭包的应用,绝大多数只有两种:函数作为返回值函数作为参数

1.函数作为返回值

function fn(){

var max=10;

return function bar(x){

if(x>max){

console.log(x)

}

}

}

var f1=fn();

f1(15); //15

2.函数作为参数

var max=10;

fn=function (x){

if(x>max){

console.log(x)

}

}

(function(){

var max=100;

fn(15)

})(fn)

这边,max的取值为10,而不是15,原因是上面提到的一个函数的作用域取值要去创建的那个作用域取值,而不是调用的那个作用域。

正常情况下,当一个函数调用结束以后,这个函数的上下文环境会被销毁,包括其中的变量,但是,闭包的核心就是不希望这个函数被销毁

未完待续..

猜你喜欢

转载自blog.csdn.net/zy21131437/article/details/83901256