本系列将从如下专题去总结:javascript
1. JS基础知识深刻总结
2. 对象高级
3. 函数高级
4. 事件对象与事件机制html
暂时会对以上四个专题去总结,如今开始Part3:函数高级。下图是我这篇的大纲。 java
this
是在函数执行的过程当中自动建立的一个指向一个对象的内部指针。确切的说,this
并非一个对象,而是指向一个已经存在的对象的指针,也能够认为是this
就是存储了某个对象的地址。面试
this
的指向不是固定的,会根据调用的不一样,而指向不一样的地方。 此章节的讲解思路是这样的: chrome
对在全局做用域中定义的变量和函数的进一步认识编程
永远记住:只要是在全局做用域声明的任何变量和函数默认都是做为window对象的属性而存在的.json
理解完以上的这句话,咱们在来讲明一下其中的区别,这是不少人没有关注过的。数组
console.log(window.a); //undefined
console.log(a); //报错!a is not defined
复制代码
注解:也就是在未对变量(a
)进行声明时,就会出现以上结果。首先明确一点,就是全局变量a
是window
的属性。因此,咱们从这里就能够发现,何时undefined
,何时报错?——那就是若是是访问一个对象的属性时,它没有声明赋值,那就是undefined
;若是访问一个变量,它没有声明赋值,那就是报错。promise
好,如今回头过来,咱们看全局做用域变量和函数的认识。浏览器
<script type="text/javascript">
var num = 10; //全局做用域声明的变量
function sum () { //全局做用域声明的函数
alert("我是一个函数");
}
alert(window.num); // 10
window.sum(); // 我是一个函数
// 在调用的时候window对象是能够省略的。
</script>
复制代码
构造函数和非构造函数的澄清
在JavaScript中构造函数和非构造函数没有本质的区别。惟一的区别只是调用方式的区别。
看一个示例代码:
<script type="text/javascript">
function Person () {
this.age = 20;
this.sex = "男";
}
//做为构造函数调用,建立一个对象。 这个时候实际上是给p添加了两个属性
var p = new Person();
alert(p.age + " " + p.sex);
//做为普通函数传递,实际上是给 window对象添加了两个属性
//任何函数本质上都是经过某个对象来调用的,若是没有直接指定就是window,也就是Window可省略
Person();
alert(window.age + " " + window.sex);
</script>
复制代码
全局做用域中使用
this
,也就是说不在任何的函数内部使用this
,那么这个时候this
就是指的 Window
<script type="text/javascript">
//全局做用域中的this
//向this对象指代的对象中添加一个属性 num, 并让属性的值为100
this.num = 100;
// 由于this就是window,因此这时是在修改属性num的值为200
window.num = 200;
alert(this === window); // true this就是指向的window对象,因此是恒等
alert(this.num); //200
alert(window.num); //200
</script>
复制代码
函数中this
又可分为构造函数和非构造函数的this
两个概念去理解。
非构造函数中的this
指向
非构造函数中
this
指向的就是 调用这个方法的那个对象
示例1:
<script type="text/javascript">
function test() {
alert(this == window);
this.age = 20;
}
test(); //实际上是 window.test(); 因此这个时候test中的this指向window
</script>
复制代码
示例2:
<script type="text/javascript">
var p = {
age : 20,
sex : "男",
sayAge: function (argument) {
alert(this.age);
}
}
p.sayAge(); //调用对象p的方法sayAge() 因此这个时候this指的是 p 这个对象
</script>
复制代码
示例3:
<script type="text/javascript">
var p = {
age : 20,
sex : "男",
sayAge: function (argument) {
alert(this.age);
alert(this === p); //true
}
}
var again = p.sayAge; //声明一个变量(方法),把p的方法复制给新的变量
//调用新的方法: 实际上是window.again().
//因此 方法中的this指代的是window对象,这个时候age属性是undefined
// this和p也是不相等的。
again();
</script>
复制代码
综上:
this
的指代和代码出现的位置无关,只和调用这个方法的对象有关。
构造方法中的
this
指代的要将来要建立的那个对象。
示例1:
<script type="text/javascript">
function Person () {
this.age = 20;
return this; //做为构造函数的时候,这个行代码默认会添加
}
var p1 = new Person(); //这个时候 Person中的this就是指的p1
var p2 = new Person(); //这是时候 Person中的this就是知道p2
</script>
复制代码
多了解一点:其实用
new
调用构造函数的时候,构造函数内部其实有个默认的return this
; 这就是为何this
指代那个要建立的对象了。
在JavaScript中,容许更改this
的指向。 经过call
方法或apply
方法
函数A能够成为指定任意对象的方法进行调用 。函数A就是函数对象,每一个函数对象中都有一个方法call
,经过call
可让你指定的对象去调用这个函数A。
ECMAScript 规范给全部函数都定义了call
与 apply
两个方法。 call
和apply
是放在Function的原型对象上的,而不是Object原型对象上!
<script type="text/javascript">
var age = 20;
function showPropertyValue (propertyName) {
alert(this[propertyName]);
}
//使用call的时候,第一个参数表示showPropertyValue中的this的执行,后面的参数为向这个函数传的值。
//注意一点:若是第一个参数是null,则this仍然是默认的指向。
showPropertyValue.call(null, "age");
showPropertyValue.call(this, "age");
showPropertyValue.call({age:50}, "age")
</script>
复制代码
在this
的指向中有第三个方向就是经过call
/apply
去改变this
的指向,这个JavaScript中一个独特的使用形式,其余语言并无。那么,咱们就在这里顺带讲一下call
、apply
以及bind
的用法。
本小节将从三个方面讲解: 1:apply
和call
的区别 2:apply
和call
的用法 3:call
和bind
的区别
ECMAScript 规范给全部函数都定义了 call
与apply
两个方法,它们的应用很是普遍,它们的做用也是如出一辙,只是传参的形式有区别而已。
简单来讲,假设有一个函数A,咱们调用函数A会直接去A()
,那么若是是A()
这样直接调用的话,函数体A里面的this
就是window
了。而咱们能够经过call
(或apply
)去调用,好比:A.call()
.这样子调用就能够指定A中的this究竟是哪一个对象。
用call
来作比对,里面有两个参数,参数一就是从新指定其中的this
是谁,参数2是属性名。而事实上,call
与apply
也就是参数二的不一样这个差别。
apply
apply
方法传入两个参数:一个是做为函数上下文的对象,简单来讲,从新指定函数中的this
是谁。另一个是做为函数参数所组成的数组,是传入一个数组。
var obj = {
name : 'ya LV'
}
function func(firstName, lastName){
console.log(firstName + ' ' + this.name + ' ' + lastName);
}
func.apply(obj, ['A', 'B']); // A ya LV B
复制代码
能够看到,obj
是做为函数上下文的对象,也就是说函数func
中 this
指向了 obj
这个对象。原本若是直接调用func()
,那么函数体中的this
就是指的是window
。可是如今有了参数一,就是从新指定this
,这个this
就是参数一的obj
这个对象。参数 A 和 B 是放在数组中传入 func
函数,分别对应 func
参数的列表元素。
call
call
方法第一个参数也是做为函数上下文的对象。与apply
没有任何区别。可是后面传入的是一个参数列表,而不是单个数组。
var obj = {
name: 'ya LV'
}
function func(firstName, lastName) {
console.log(firstName + ' ' + this.name + ' ' + lastName);
}
func.call(obj, 'C', 'D'); // C ya LV D
复制代码
对比apply
咱们能够看到区别,C 和 D 是做为单独的参数传给 func
函数,而不是放到数组中。
对于何时该用什么方法,其实不用纠结。若是你的参数原本就存在一个数组中,那天然就用apply
,若是参数比较散乱相互之间没什么关联,就用 call
。
补充一个使用
apply
的例子。好比求一个数组的最大值?
明确JavaScript中没有返回一个数组中最大值的函数。可是,有一个函数Math.max
能够返回任意多个数值类型的
参数中的最大值,Math.max
函数入参并不支持数组,只能是将多个参数逐个传入,用逗号分隔。
这个时候若是要能够用Math.max函数,且传参数能够是一个数组。咱们天然而然会想到全部函数都定义了call
和apply
的方法,咱们能够配合apply
或call
来实现,又由于call
传参数并非一个数组。全部咱们就选择出Math.max
函数加上apply
就能够实现咱们的目的。
本来只能这样用,并不能直接用数组,见示例:
let max = Math.max(1, 4, 8, 9, 0)
复制代码
有了 apply
,就能够这么调用:
let arr = [1, 4, 8, 9, 0];
let max = Math.max.apply(null, arr);
复制代码
在调用apply
的时候第一个参数给了一个null
,这个是由于没有对象去调用这个方法,咱们只须要用这个方法帮咱们运算,获得返回的结果就行,因此就直接传递了一个null
过去。
apply
和call
的用法能够分为三个:改变this
的指向,借用别的对象的方法,调用函数。
var obj = {
name: 'ya LV'
}
function func() {
console.log(this.name);
}
func.call(obj); // ya LV
复制代码
这个在前一小节有讲到,因此咱们就简单的再来看看。所谓“熟能生巧”,同样东西,一个知识点,每看一次会有不一样的体会,可能此次看的过程让你有更深入的思考,这就是进步。call
方法的第一个参数是做为函数上下文的对象,这里把 obj
做为参数传给了func
,此时函数里的this
便指向了obj
对象。此处func
函数里其实至关于:
function func() {
console.log(obj.name);
}
复制代码
另外注意下call
的一些特别用法,很奇葩的this
指向。稍微注意下,有点印象就好。
function func() {
console.log(this);
}
func.call(); //window
func.call(undefined); //window
func.call(null); //window
func.call(1); //Number {1} 这种状况会自动转换为包装类Number 就至关于下面一行代码
func.call(new Number(1)); //Number {1}
复制代码
var Person1 = function () {
this.name = 'ya LV';
}
var Person2 = function () {
this.getname = function () {
console.log(this.name);
}
Person1.call(this);
}
var person = new Person2();
person.getname(); // ya LV
复制代码
从上面咱们看到,Person2
实例化出来的对象 person
经过 getname
方法拿到了 Person1
中的 name
。由于在 Person2
中,Person1.call(this)
的做用就是使用Person1
对象代替 this
对象,那么 Person2
就有了Person1
中的全部属性和方法了,至关于 Person2
继承了Person1
的属性和方法。 不理解的话咱们再来慢慢看,咱们说A.call ( 参数一)
这样的形式就是从新指定函数A
中的this
是‘参数一’
这个对象,那么咱们来看看Person2
函数体中的Person1.call(this)
这条语句,其中这条语句的this
是指Person2
这个对象。如今就是把Person1
函数的this
从新指向为Person2
,是否是有了Person2.name='ya LV'
。
apply
、call
方法都会使函数当即执行,所以它们也能够用来调用函数。这个咱们在这节的一开始就有说,好比A()
和A.call()
都是调用函数A。
function func() {
console.log('ya LV');
}
func.call(); // ya LV
复制代码
在 EcmaScript5
中扩展了叫 bind
的方法,在低版本的 IE 中不兼容。它和call
很类似,接受的参数有两部 分,第一个参数是是做为函数上下文的对象,第二部分参数是个列表,能够接受多个参数。
它们之间的区别有如下两点。
var name='HELLO'
var obj = {
name: 'ya LV'
}
function func() {
console.log(this.name);
}
//将func的代码拷贝一份,而且永远改变其拷贝出来的函数中的this,为bind第一个参数所指向的对象。把这 份永远改变着this指向的函数返回给func1.
var func1 = func.bind(obj);
//bind方法不会当即执行,是返回一个改变上下文this的函数,要对这个函数调用才会执行。
func1(); //ya LV
//能够看到,如今这份改变this以后拷贝过来的函数,this的指向永远是bind()绑定的那个,无论以后去call 从新指向对象,func1 都不会改变this的指向。永远!可知,bind比call优先级还高。
func1.call({name:'CALL'}); //ya LV
//又从func从新拷贝一份永远改变this指向对象为{name:'LI SI'}这个对象的函数,返回给func2.
var func2 = func.bind({name:'LI SI'});
func2(); //LI SI
//注意,这里是拷贝一份func2(而不是func)的代码,而func2以前已经绑定过去永远改变this的指向了,因此这 里并不去改变!仍是会输出原来的最早bind的this指向对象。
var func3 = func2.bind({name:'ZHANG SAN'});
func3(); //LI SI
//上面对func最初的函数进行了屡次绑定,绑定后原函数 func 中的 this 并无被改变,依旧指向全局对象 window。由于绑定bind的过程是拷贝代码的一个过程,而不是在其自身上修改。window.name = HELLO
func(); //HELLO
复制代码
bind
方法不会当即执行,而是返回一个改变了上下文this
后的函数。而原函数func
中的 this
并无被改变,依旧指向全局对象 window
。
function func(a, b, c) {
console.log(a, b, c);
}
var func1 = func.bind(null,'yaLV');
func('A', 'B', 'C'); // A B C
func1('A', 'B', 'C'); // yaLV A B
func1('B', 'C'); // yaLV B C
func.call(null, 'yaLV'); // yaLV undefined undefined
复制代码
call
是把第二个及之后的参数做为 func
方法的实参传进去,而 func1
方法的实参实则是在bind
中参数的基础上再日后排。也就是说,var func1 = func.bind(null,'yaLV');
bind
现有两个参数,第一个是指向,第二个实参是'yaLV'
,那么就是先让func
中的a='yaLV'
,而后没排满就是让func1('A', 'B', 'C')
; 这个参数依次排,如今b='A'
,c='B'
, 形参已经排完了。也就是输出yaLV A B
。
在低版本浏览器没有bind
方法,咱们也能够本身实现一个。
if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var self = this, // 保存原函数
context = [].shift.call(arguments), // 保存须要绑定的this上下文
args = [].slice.call(arguments); // 剩余的参数转为数组
return function () { // 返回一个新函数
self.apply(context[].concat.call(args[].slice.call(arguments));
}
}
}
复制代码
习题1
<script type="text/javascript">
var name='window_dqs';
var obj={
name:'obj_dqs',
showName:function(){
console.log(this.name);
}};
function fn(){
console.log(this);
}
function fn2(){
this.name='fn_dqs';
}
//由于obj去调用,this就是obj
obj.showName(); //obj_dqs
//由于借调,而此时借调的对象是this,而this在全局做用域上就是指window,因此找window.name
obj.showName.apply(this); //window_dqs
//由于借调的对象是一个函数对象,那么this就是指函数对象,this.name就是函数名
obj.showName.apply(fn2); //fn2
</script>
复制代码
习题2
<script type="text/javascript">
var name='window_dqs';
function fn(){
this.name='fn_dqs';
this.showName=function(){
console.log(this.name);
}
console.log(this);
}
function fn2(){
this.name='fn_pps';
this.showName=function(){
console.log(this.name);
}
console.log(this);
}
var p=new fn();
fn2.apply(p);
p.showName();
var obj={};
fn2.apply(obj);
obj.showName();
</script>
复制代码
结果是:
var p=new fn();输出fn { name: 'fn_dqs', showName: [Function] }
fn2.apply(p);输出fn { name: 'fn_pps', showName: [Function] }
p.showName();输出fn_pps
var obj={};
fn2.apply(obj);输出Object{name: "fn_pps"showName: ƒ ()__proto__: Object··}
obj.showName();输出fn_pps
复制代码
习题3
<script type="text/javascript">
var name='window_dqs';
var obj={
name:'json_dqs',
showName:function(){
console.log(this.name);
return function(){
console.log(this.name);
}
}
}
var p=obj.showName();
obj.showName()();
p.call(obj);
</script>
复制代码
结果是:
json_dqs
json_dqs
window_dqs
json_dqs
复制代码
面试题1
代码片断1:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function (){
return function (){
return this.name;
};
}
};
console.log(object.getNameFunc()); //ƒ (){return this.name;}
console.log(object.getNameFunc()()); //The Window
复制代码
代码片断一没有闭包。有嵌套,但没有用外部函数的变量或函数。是使用this
的。this
与调用方式有关。
理解:看object.getNameFunc()
是对象.方法() 返回的是一个函数,这个函数还未执行。js中this
是动态的,因此函数没有执行,并不肯定函数里的this
是指的是谁?那么如今再对返回的函数加个(),也就是object.getNameFunc()()
,调用执行,把最后一个括号和最后一个括号前当作两个部分,前面是函数名,后面一个括号是调用。至关于test()
,这个时候this
就是window
。故这样调用的函数this就是指的window
,故window.name=The Window
.
代码片断二: 对于片断一咱们的本意是否是想输出My Object
。那么怎么改造,经过that=this
去操做。
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function () {
var that = this; //缓存this
return function () {
return that.name2;
};
}
};
console.log(object2.getNameFunc()); //ƒ (){return that.name2;}
console.log(object2.getNameFunc()()); //My Object
复制代码
代码片断二是有闭包的,有嵌套函数。内部函数有使用外部函数的变量that
。外部和内部函数有执行。
理解:首先仍是看object2.getNameFunc()
返回一个函数,注意这个函数中没有this
,在调用object2.getNameFunc
时,咱们有执行一句var that = this
;也就是把this
给that
,这个时候this
是指的是object2
。再次调用object2.getNameFunc()()
时就是执行object2.getNameFunc()
返回来的函数”。that.name2=object2.name2
;实质上是闭包,使用了外部函数的that
变量。
代码片断三(对片断二的改造):
var name3 = "The Window";
var object3 = {
name3: "My Object",
getNameFunc: function () {
return function () {
return this.name3;
}.bind(this);
}
};
console.log(object3.getNameFunc()); //ƒ (){return this.name3;}
console.log(object3.getNameFunc()()); //My Object
复制代码
理解:与“代码片断二”同样,只是片断二是经过that=this
去改变this
的值,而片断三是经过bind
绑定this
的值。看bind(this)
这里的this
就是指这条语句object3.getNameFunc()
调用的对象object3
.因此经过这个手段去把this
指向了前面的对象object3
.再去调用返回的函数时,那么this.name3=object3.name3
。
面试题2
<script>
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo); //bar
console.log(self.foo); //bar
(function() {
console.log(this.foo); //undefined 此时的this是window
console.log(self.foo); //bar 闭包能够看到外部的局部变量
}()); //匿名函数自执行,是window上调用这个函数。
}
};
myObject.func();
//那么如何修改呢?使得在自执行函数中的this.foo就是咱们想要的bar呢?
//提供两种方法:
//case1:用call去指向this是谁
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo); // bar
console.log(self.foo); // bar
(function() {
console.log(this.foo); // bar
console.log(self.foo); // bar
}.call(this));
//myObject.func();这样调用func(),那么func()中的this就是前面的对象myObject。
}
};
myObject.func();
//case2:用bind去绑定this,但要注意bind是返回一个函数,故要bind(this)(),后一个括号表示函数调用。把bind(this)将拷贝一份并改变this的指向的函数执行。
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo); // bar
console.log(self.foo); // bar
(function() {
console.log(this.foo); // bar
console.log(self.foo); // bar
}.bind(this)());
}
};
myObject.func();
</script>
复制代码
面试3 考察this
的指向: 难点:数组(类数组)中的元素当作函数调用时的this
指向 也就是,若是是调用数组(类数组)中的元素, 元素函数中的this
是这个数组(类数组)。
<script>
var length = 10;
function fn(){
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn){ // [fn, 1, 2]
fn(); // 10
arguments[0](); // 3
}
};
obj.method(fn, 1, 2);
/*obj.method(fn, 1, 2);传实参fn过去,此时fn拿到函数的地址值拷贝给形参fn,在执行fn()这里调用是至关window调用fn,this指的是window。而不是obj不要感受是在obj里面就是,迷惑你们的。this的指向永远跟调用方式有关。
*另外,arguments[0]();调用时,这个时候是类数组中的元素调用,那么这时的this是类数组自己,因此,数组.length是否是输出类数组的长度。
*若是是调用数组(类数组)中的元素, 元素函数中的this是这个数组(类数组).为何呢?看如下两个例子:
* */
//例子1:
var obj = {
age : 100,
foo : function (){
console.log(this);
}
}
var ff = obj.foo;
ff(); //window
obj.foo(); //{age: 100, foo: ƒ}
obj["foo"](); //{age: 100, foo: ƒ}
//上面的这个例子没有问题吧。很天然的。
//例子2:
var arr = [
function (){
console.log(this);
},function (){
}
];
var f = arr[0];
f(); //window
/*arr.0()--相似于这么写把,只是数组不容许这样的语法--*/
arr[0](); //输出数组自己:(2) [ƒ, ƒ] 。故验证一句话:若是调用数组(类数组)中的元素时,那么这时的this是数组(类数组)自己。
</script>
复制代码
一个特别经典的总结:
从
a.b
就能够看出做用域与做用域链,原型与原型链的知识。 (详见Part1的1.1.3)
构造函数建立对象咱们先使用构造函数建立一个对象:
function Person() {
}
var person = new Person();
person.name = 'name';
console.log(person.name) // name
复制代码
在这个例子中,Person
就是一个构造函数,咱们使用new建立了一个实例对象person
。
很简单吧,接下来进入正题:【prototype】
任何的函数都有一个属性prototype
,这个属性的值是一个对象,这个对象就称为这个函数的原型对象。可是通常状况,咱们只关注构造函数的原型。好比:
function Person() {
}
// 虽然写在注释里,可是你要注意:prototype是函数才会有的属性
Person.prototype.name = 'name';
var person1 = new Person(); //person1是Person构造函数的实例
var person2 = new Person(); //person2是Person构造函数的实例
console.log(person1.name) // name
console.log(person2.name) // name
复制代码
其实,函数的prototype
属性指向了一个对象,这个对象正是调用该构造函数而建立的实例的原型,也就是这个例子中的person1
和person2
的原型。实例实际上是经过一个不可见的属性[[proto]]
指向的。
你能够这样理解:每个JavaScript对象(null
除外)在建立的时候就会与之关联另外一个对象,这个对象就是咱们所说的原型,每个对象都会从原型”继承”属性。
让咱们用一张图表示构造函数和实例原型之间的关系:
person1
person2
和
Person.prototype
之间的关系呢,这时候咱们就要讲到第二个属性:
[[proto]]
当使用构造函数建立对象的时候, 新建立的对象会有一个不可见的属性[[proto]], 他会指向构造函数的那个原型对象。事实上,每个JavaScript对象(除了null)都具备的一个不可见属性,叫[[proto]],这个属性会指向该对象的原型。
为了证实这一点,咱们能够在火狐或者谷歌中输入:
function Person() {
}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype); //true
复制代码
因而咱们更新下关系图:
constructor
属性指向关联的构造函数。
为了验证这一点,咱们能够尝试:
function Person() {
}
console.log(Person === Person.prototype.constructor); //true
复制代码
因此再更新下关系图:
function Person() {
}
var person1 = new Person();
//对象的__proto__属性: 建立对象时自动添加的, 默认值为构造函数的prototype属性值(很重要)
console.log(person1.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,能够得到对象的原型
console.log(Object.getPrototypeOf(person1) === Person.prototype) //true
复制代码
了解了构造函数、实例原型、和实例之间的关系,接下来咱们讲讲实例和原型的关系:【实例与原型】。当读取实例的属性时,若是找不到,就会查找与对象关联的原型中的属性,若是还查不到,就去找原型的原型,一直找到最顶层为止。
举个例子:
function Person() {
}
//往Person对象原型中添加一个属性
Person.prototype.name = 'name';
//建立一个person1实例对象
var person1 = new Person();
//给建立的实例对象person1添加一个属性
person1.name = 'name of this person1';
//查找person1.name,由于自己实例对象有,那么就找到了自身实例对象上的属性和属性值
console.log(person1.name) // name of this person1
//删除实例对象的属性和属性值
delete person1.name;
//查找属性name,在实例对象自身上找不到,经过proto指向往原型链上找,在原型对象中找到
console.log(person1.name) // name
复制代码
在这个例子中,咱们设置了person1
的name
属性,因此咱们能够读取到为name of this person1
,当咱们删除了person1
的name
属性时,读取person1.name
,从person1
中找不到就会从person
的原型也就是person.__proto__ == Person.prototype
中查找,幸运的是咱们找到了为name
,可是万一尚未找到呢?原型的原型又是什么呢?
var obj = new Object();
obj.name = 'name'
console.log(obj.name) // name
复制代码
因此原型对象是经过Object
构造函数生成的,结合以前所讲的一句很重要的话,几乎就是涵盖原型与原型链知识的始终的一句话,那就是:实例对象的proto
指向构造函数的prototype
。也就是说,Person.prototype
这个原型对象(实例原型)是经过Object
这个构造函数new出来的,也就是Person.prototype这个原型对象是Object
的实例,因此这个实例会有proto
属性指向Object
构造函数的原型对象Object.prototype
。
这里呢插入一句总结出来的话,逆推顺推都是可行的,那就是:实例经过proto
这个属性指向其构造函数的原型对象。因此咱们再更新下关系图:
Object.prototype
的原型呢?
null
,嗯,就是
null
。因此查到
Object.prototype
就能够中止查找了。因此最后一张关系图就是:
那【原型链】是啥 ? 那就是由proto
这个属性进行查找的一个方向这就是一条原型链。图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线,都是经过proto
属性进行查找的。
那么访问一个对象的属性时,怎么经过原型链去查找属性或方法呢 ? 先在自身属性中查找,找到返回。若是没有, 再沿着proto
这条链向上查找, 找到返回。若是最终没找到, 返回undefined
。
n
,
s
都是全局变量,而后经过对象字面量的方法去建立了一个对象。而后有一个构造函数(之因此是构造函数,是由于后面代码有
new
的操做),这个构造函数就会有函数声明提早,当构造函数声明时,就会去在内存中建立一个
person
的函数对象,这个函数对象里
只有prototype
属性,去指向
person
的函数原型对象。要注意,如今尚未去执行里面的代码,只是函数声明时建立了一个
person
的函数对象。后面就是new的实例对象,新
new
出来的实例对象
P2
P3
就会在内存中分配一块内存去把地址值给它,如今才会去执行构造函数中的代码。因此只有
P2
P3
才会 有
name
age
speak
属性和方法。这些新
new
出来的实例对象就会有一个不可见的属性
proto
,去指向这个原型对象。而最终这个
person
的函数原型对象会有指向一个
object
的原型对象,再上去其实就是
null
。这就一层一层往上走就是原型链,由于原型链,咱们才会有继承的特性。
注几点:
P2
P3
实例对象虽然使用的是 Person
构造函数,可是对象建立出来以后,这个P2
P3
实例对象其实已经与 Person
构造函数(函数对象)没有任何关系了,P2
P3
实例对象的[[ proto ]]
属性指向的是 Person
构造函数的原型对象。new Person()
建立多个对象,则多个对象都会同时指向 Person
构造函数的原型对象。P2
P3
····这些实例对象就会共享这些在原型中添加的属性和方法。也就是说,原型对象至关于公共的区域,全部的同一类的实例均可以去访问到原型对象。P2
实例对象 中的一个属性 gender
,若是在P2
对象中找到,则直接返回。若是 P2
对象中没有找到,则直接去P2
对象的 [[proto]]
属性指向的原型对象中查找,若是查找到则返回。(若是原型中也没有找到,则继续向上找原型的原型---原型链)。P2
对象只能读取原型中的属性 name
的值,并不能修改原型中的属性name
的值。 P2.gender= "male"
; 并非修改了原型中的值,而是在 P2
对象中给添加了一个属性 gender
。另外看看,原型与原型链的三点关注:
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
复制代码
console.log(Function.__proto__===Function.prototype) //true
复制代码
console.log(Object.prototype.__proto__) // null
复制代码
instanceof
instanceof
是如何判断的?
表达式: A instanceof B
若是B构造函数的原型对象(B.prototype
)在A实例对象的原型链(A.proto.proto·····
沿着原型链)上, 返回true
, 不然返回false
。(见下图)
也就是说A实例对象的原型链上可能会有不少对象,只要B构造函数的原型对象有一个是在其原型链上的对象便可返回true
。
反过来讲也同样,实例对象A是否能够经过proto
属性(沿着原型链,A.proto.proto·····
)找到B.prototype
(B的原型对象),找到返回true
,没找到返回false
.
注1:对实例对象的说明,事实上,实例对象有两种。一种是咱们常常说的new
出来的实例对象(好比构造函数Person
, new
出来p1
p2
...,这些都是实例对象),另一种就是函数,函数自己也是实例,是Function
new
出来的。但咱们通常说的实例对象就是指new
出来的相似于p1
p2
这些的实例对象。
Function
是经过new
本身产生的实例(Function.proto===Function.prototype)
案例1:
//一个构造函数Foo
function Foo() { }
//一个f1实例对象
var f1 = new Foo()
//翻译:f1是Foo的实例对象吗?
//还记得我说过,一个实例对象经过proto指向其构造函数的原型对象上。
//深刻翻译:f1这个实例对象经过proto指向是否能够找到Foo.prototype上呢?
console.log(f1 instanceof Foo) // true
//这行代码能够得出,沿着proto只找了一层就找到了。
console.log(f1.__proto__ === Foo.prototype); // true
//翻译:f1是Object的实例对象吗?
//深刻翻译:f1这个实例对象经过proto指向是否能够找到Object.prototype上呢?
console.log(f1 instanceof Object) // true
//这两行代码能够得出,沿着proto找了两层才找到。事实上,f1.__proto__找到了Foo.prototype(Foo构造函数原型上),再次去.__proto__,找到了Object的原型对象上。见下图。
console.log(f1.__proto__ === Object.prototype); // false
console.log(f1.__proto__.__proto__ === Object.prototype); // true
复制代码
//这个案例的实质仍是那句话:一个实例对象经过proto属性指向其构造函数的原型对象上。
//翻译:实例对象Object是否能够经过proto属性(沿着原型链)找到Function.prototype(Function的原型对象)
console.log(Object instanceof Function) // true
//以上结果的输出能够看到下图,Object.__proto__直接找到一层就是Function.prototype.(Object created by Function)可知Object构造函数是由Function建立出来的,也就是说,Object这个实例是new Function出来的。
console.log(Object instanceof Object) // true
//颇有意思。上面咱们已经知道Object这个实例是new Function出来的。也就是Object.proto指向Function.prototype。有意思的是,Function的原型对象又是Object原型对象的一个实例,也就是Function.prototype.proto 指向 Object.prototype .颇有意思吧,见下图很更清楚这个“走向”。
console.log(Function instanceof Function) // true
//由这个可知,能够验证咱们的结论:Function是经过new本身产生的实例。 Function.proto===Function.prototype
console.log(Function instanceof Object) // true
//Function.proto.proto===Function.prototype (找了两层)
//定义了一个Foo构造函数。由下图可知,Foo.proto.proto.proto===null
function Foo() {}
console.log(Object instanceof Foo) // false
//这条语句要验证的是,Object是否能够经过其原型链找到Foo.prototype。
// Object.proto.proto.proto=null 并不会找到Foo.prototype。因此,返回FALSE。
复制代码
(Object created by Function)
也就是说,对象是
new Function
获得的。 继续翻译,对象是实例
Function
是构造函数。 继续翻译,对象这个实例有不可见属性
proto
指向
Function
构造函数的原型对象
(Function.prototype)
。 故,函数与对象的关系是:函数更大,它包含对象。 这个我我的以为很重要,务必理解透。
prototype
: 显式原型属性__proto__
: 隐式原型属性{}
, 即用为原型对象__proto__
: 在建立实例对象时被自动添加, 并赋值为构造函数的prototype值__proto__
属性, 它指向的就是原型对象__proto__
属性就造成了一个链的结构---->原型链面试1:
/*阿里面试题*/
①function Person(){
②getAge = function (){
console.log(10)
}
③return this;
}
④Person.getAge = function (){
console.log(20);
}
⑤Person.prototype.getAge = function (){
console.log(30);
}
⑥var getAge = function (){
console.log(40)
}
⑦function getAge(){
console.log(50)
}
Q1:Person.getAge() // 20
Q2:getAge() // 40
Q3:Person().getAge() // 10
Q4:getAge() // 10
Q5:new Person().getAge() // 30
Q6:new Person.getAge(); // 20
复制代码
总体代码块①定义了构造函数Person
②是在构造函数中有一个未声明的变量,这个变量是引用变量,内容为地址值。指向一个函数对象。又由于,未使用严格模式下,在函数中不使用var
声明的变狼都会成为全局变量。(注意这里不是属性,是全局变量)同时也要注意,这里②和③的语句在解析到这里后并无执行。执行的话就要看有没有new
(做为构造函数使用),或者有没有加()调用(做为普通函数使用)。 ③返回一个this
。这个this
是谁如今还不知道。须要明白js中的this
是动态的,因此根据上一节this
的总结才定位到this
究竟是谁。 ④Person.getAge
是典型的“对象.属性(方法)”的形式,因此它是给Person
函数对象上添加一个getAge
的方法。等同于:
function Person.getAge(){
console.log(20);
}
复制代码
函数名其实就是变量名。 ⑤在构造函数的原型中添加了getAge
的方法 ⑥这里也是给一个全局变量赋值一个地址值,使其指向一个函数对象。注意,这里var的变量会声明提早。与代码块②区别,这里当解析完后,getAge
已经指向一个函数对象啦。能够看作:
function getAge(){
console.log(40)
}
复制代码
⑦定义一个函数,函数也会声明提早。在栈内存有getAge
,内容值为一个地址值,指向一个函数对象。
Q1:对象.属性方法()。代码块④产生的结果。 Q2:调用函数,全局做用域里的。那只有代码块⑥产生结果。 Q3:Person().getAge()
。先看前面一部分Person()
,把Person
当作一个普通函数调用,执行Person
函数体对全局变量getAge
进行定义并从新指向,也就是Person()
执行了代码块②而覆盖了代码块⑥的操做。又返回this,根据Person()
这种调用方式,可知this就是window
。因此就是“window.gerAge()”,因被覆盖了,因此这行代码执行结果是代码块②产生。 Q4:getAge()
至关于window.getAge()
; 仍是上一个语句的结果,代码块②产生结果。 Q5:new Person()
先看这部分,就是new出来一个实例,你能够想成p1
,那么p1.getAge();
p1
是一个Person
的实例,p1
中有不可见的[[proto]]
属性,指向Person
的原型对象。那么p1.getAge ()
,如今p1
自己找,找不到就沿着原型链(proto
指向链)去找,好找到了原型对向中有,由于代码块⑤产生做用。 Q6:new Person.getAge();
能够把Person.getAge
当作一个对象,去new
它,是否是相似于咱们日常var p1=new Person()
;这样的操做,因此咱们把Person.getAge
看作一个构造函数去new
它。由上面对代码块④的理解,能够看作那样的函数,因此结果就是代码块④产生的结果。
面试题2:
function A () {
}
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
};
var c = new A();
console.log(b.n, b.m, c.n, c.m); //1 undefined 2 3
//见下图:
复制代码
//与上题的区别在于如何理解a.x的执行顺序
<script>
var a = {n: 1};
var b = a;
a.x = a = {n: 2}; //先定义a.x再去从右往左赋值操做。
console.log(a.x); // undefined 对象.属性 找不到 是返回undefined 变量找不到则报错!
console.log(b); // {n :1, x : {n : 2}}
</script>
//见下图分析
复制代码
面试题4:
//构造函数F
function F (){};
Object.prototype.a = function(){
console.log('a()')
};
Function.prototype.b = function(){
console.log('b()')
};
//new一个实例对象f
var f = new F();
f.a(); //a()
f.b(); //报错,找不到
F.a(); //a()
F.b(); //b()
复制代码
变量声明提高
var
定义(声明)的变量, 在定义语句以前就能够访问到undefined
console.log(c); //报错,c is not defined.
console.log(b); //undefined
var b=0;
c=4;
console.log(c); //4 意外的全局变量->在ES5的严格模式下就会报错。
console.log(b); //0
复制代码
函数声明提高
先有变量提高, 再有函数提高
案例一:
var a = 3;
function fn () {
console.log(a); //undefined
var a = 4
}
fn();
//上面这段代码至关于
var a = 3;
function fn () {
var a;
console.log(a); //undefined
a = 4
}
fn();
复制代码
案例二:
console.log(b) //undefined 变量提高
fn2() //可调用 函数提高
fn3() //不能调用,会报错。 fn3是一个函数表达式,并不会函数提高,实际上他是变量提高。
var b = 3
function fn2() {
console.log('fn2()')
}
var fn3 = function () {
console.log('fn3()')
}
复制代码
问题: 变量提高和函数提高是如何产生的? An:由于存在全局执行上下文和函数执行上下文的预处理过程。因此咱们就来学习下一节的执行上下文。
代码分类(位置)
执行上下文分为全局执行上下文和函数执行上下文
全局执行上下文
window
肯定为全局执行上下文对象(虚拟的)var
定义的全局变量==>值为undefined
, 并添加为window
的属性function
声明的全局函数==>赋值(fun
), 添加为window
的方法this
==>赋值(window
)//全局执行上下文
console.log(a1); //undefined
console.log(a2); //undefined
a2(); //也会报错,a2不是一个函数
console.log(a3); //ƒ a3() {console.log('a3()')}
console.log(a4) //报错,a4没有定义
console.log(this); //window
var a1 = 3;
//函数表达式,其实是变量提高。而不是函数提高。
var a2 = function () {
console.log('a2()')
};
function a3() {
console.log('a3()')
}
a4 = 4;
复制代码
函数执行上下文
arguments
==>赋值(实参列表), 添加为执行上下文的属性var
定义的局部变量==>undefined
, 添加为执行上下文的属性function
声明的函数 ==>赋值(fun
), 添加为执行上下文的方法this
==>赋值(调用函数的对象)//函数执行上下文
function fn(a1) {
console.log(a1); //2 实参对形参赋值
console.log(a2); //undefined 函数内部局部变量声明提高
a3(); //a3() 可调用 函数提高
console.log(arguments); //类数组[2,3]
console.log(this); //window
var a2=3;
function a3() {
console.log("a3()");
}
}
fn(2,3); //执行,不执行不会产生函数执行上下文
复制代码
全局执行上下文和函数执行上下文的生命周期
全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
函数 : 调用函数时产生, 函数执行完时死亡
window
)肯定后, 将其添加到栈中(压栈)window
<script type="text/javascript">
//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(10); //2. 进入bar函数执行上下文(注:函数执行上下文对象在函数调用时产生,而不是函数声明时产生)
</script>
复制代码
以上这种状况整个过程产生了3个执行上下文 调用一次函数产生一个执行上下文 若是在上面代码最后一行的bar(10)
,再调用一次bar(10)
,那么就会产生5个上下文。 由于第一个bar(10)
产生一个函数上下文 在bar
函数中调用foo
,又产生一个函数执行上下文。 那么如今又调用bar(10)
,与上面一个样会产生两个上下文,加起来4个函数执行上下文。 最后加上window
的全局变量上下文,一共五个执行上下文。
window
的全局上下文,而后执行
bar()
会把
bar
函数执行上下文压入栈中。
bar
中调用
foo
,把
foo
函数执行上下文压入栈中,
foo
函数执行完毕,释放,便会把
foo
函数执行上下文
pop
(推出来)。逐渐
bar
执行完毕,
pop
出
bar
函数执行上下文,最后只剩下
window
上下文。
注解2:假设一个状况:f1()
函数中会调用f2()
和f3()
函数。那么在当前时刻栈中可最多达到几个上下文? An: 当f1()
执行,会先调用f2()
,调用完后,f2()
已经完成了使命,它的生命周期就结束了,因此栈 就会释放掉他,在执行f3()
,因此栈中也就最多三个上下文。f3()
f1()
window
.
注解3:假设另外一个状况:f1()
函数中会调用f2()
, f2()
中会调用f3()
函数。那么在当前时刻栈中可最多达到几个上下文 ? An: 当f1()
执行,会先调用f2()
,执行f2()
时要调用f3()
,因此,栈中可达到4个上下文。f3()
f2()
f1()
window .
面试题1:执行上下文栈
<script type="text/javascript">
console.log('global begin: '+ i); //undefined 变量提高
var i = 1;
foo(1);
function foo(i) {
if (i == 4) {
return;
}
console.log('foo() begin:' + i);
foo(i + 1);
console.log('foo() end:' + i);
}
console.log('global end: ' + i) //1 全局变量i,其余的函数中的i当执行结束后就销毁了。
复制代码
执行结果:
 global begin: undefined
foo() begin:1
foo() begin:2
 foo() begin:3
 foo() end:3
 foo() end:2
 foo() end:1
 global end: 1
复制代码
一共产生5个上下文: 分析见下图,我画的很清楚了。这张图画了12min。主要就是入栈出栈,在出栈前,回溯原来的那个函数,那个函数执行上下文还在,若是还有 没有执行完的语句 会在这个时候执行。当剩余的语句已经执行完了,那么这个函数的执行上下文生命周期结束,释放出栈。想一想咱们递归调用去求阶乘的例子,思想是同样的。
面试题2:变量提高和函数提高(执行上下文)
function fn(a){
console.log(a); // 输出function a的源码,a此时是函数a
var a = 2;
function a(){
}
console.log(a); // 2
}
fn(1);
复制代码
考察点: 声明提早 难点: 函数优先
调用一开始, 就会先建立一个局部变量a, (由于a是形参), 而后把实参的值1赋值给a
,a= 1
几乎在同一时刻,那么一瞬间,开始处理函数内变量提高和函数提高 此时,a
由于函数提高已经变成了a = function(){}
以上这些过程都是函数执行上下文的预处理过程 接下来,才是正式执行内部函数的代码。 console.log(a);
此时输出的就是function源码ƒ a(){}
结尾的输出语句便输出a = 2
。
测试题1: [考查知识点]先预处理变量, 后预处理函数
function a() {} //函数提高
var a; //变量提高
//先预处理变量, 后预处理函数。也就是,函数提高会覆盖变量提高。
console.log(typeof a); //function
复制代码
测试题2:[考查知识点] 变量预处理, in操做符 (在window上能不能找到b,无论有没有值)
if (!(b in window)) {
var b = 1;
//在ES6以前没有块级做用域,因此这个变量b 至关于window的全局变量
}
console.log(b); //undefined
复制代码
测试题3: [考查知识点]预处理, 顺序执行 这个题笔者认为出的至关好。混乱读者的视角。固然再次强调,面试题是专门命题出来考查的,实际开发上可能有些不会这么用。但主要做用就是深刻理解。
var c = 1;
function c(c) {
console.log(c);
var c = 3;
}
c(2); //报错。 c is not a function
//这个题包含了变量和函数声明提高的问题,就是等价于如下的代码:
var c; //变量提高
function c(c) { //函数提高,覆盖变量提高
console.log(c);
var c = 3; //函数内部的局部变量(在栈内存的封闭内存空间里,外面看不到)
}
c=1;//开始真正执行代码var c = 1
console.log(c);
c(2); //c is not a function c是一个变量,值为number类型的数值.怎么能够执行?
复制代码
1.理解:
2.分类:
ES6
有了!)3.做用
b
,那么在函数体中能不能有变量b
,固然能够,这就是分隔变量。给个案例:
var a = 10,
b = 20
function fn(x) {
var a = 100,
c = 300;
console.log('fn()', a, b, c, x)
function bar(x) {
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x)
}
bar(100)
bar(200)
}
fn(10);
复制代码
输出结果:
fn() 100 20 300 10
bar() 1000 20 300 400 100
bar() 1000 20 300 400 200
复制代码
4.做用域的图解以下:
1.区别1
2.区别2
3.联系
4.做用域与执行上下文图解以下:
1.理解
2.查找一个变量的查找规则
3.做用域链的图解以下:
面试题1:做用域
<script type="text/javascript">
var x = 10;
function fn() {
console.log(x); //10
}
function show(f) {
var x = 20;
f();
}
show(fn);
</script>
复制代码
记住: 做用域是代码一编写就肯定下来了,不会改变。产生多少个做用域?n+1
. n
就是多少个函数,1
就是指的是window
。查找变量就是沿着做用域查找,而做用域是一开始就肯定了,与哪里调用一点关系都没有。 见图解:
<script type="text/javascript">
var fn = function () {
console.log(fn) //output: ƒ () {console.log(fn)}
}
fn()
var obj = {
fn2: function () {
console.log(fn2) //报错,fn2 is not defined
console.log(this.fn2)//输出fn2这个函数对象
}
}
obj.fn2()
</script>
复制代码
报错缘由:由于首先在这个匿名函数做用域找,找不到去上一层全局找,没找到,报错。找fn2
是沿着做用域查找的! 输出fn2
这个函数对象的缘由:若是要找到obj
属性fn2
,则用this.fn2()
,让其用this
这个指针指向obj
,在obj
这个对象中找fn2
属性。
面试题3:考察连续赋值隐含的含义
(function(){
var a = b = 3;
})();
console.log(a); // 报错 a is not defined
console.log(b); // 3
复制代码
理解:首先,赋值从右向左看。b = 3
由于没var 因此至关于在全局做用域中添加b
,赋值为3
。如今。看前面var a=
的部分,a有var,那么a就是局部变量放在栈内存的封闭内存空间上。var a=b
,b
是变量,是基本数据类型的变量。它的内存中的内容值就是基本数据类型值,故拷贝一份给a
。局部变量中a=3
匿名函数自执行,会有一个函数执行上下文对象。当函数执行完成,就会把执行上下文栈弹出这个上下文对象。就再也访问不到。
因此,在函数自执行结束后,再执行输出a
的语句。a
压根就找不到,根本没定义。b
由于是全局变量仍是能够找到滴。
注意扩展,这里只有在非严格模式下,才会把b
当作全局变量。若在ES5中严格模式下,则会报错。
面试4:考虑返回值问题
function foo1(){
return {
bar: "hello"
}
}
function foo2(){
return
{
bar: "hello"
}
}
console.log(foo1()); // 返回一个对象 {bar:'hello'}
console.log(foo2()); // undefined
复制代码
解释:由于foo2
函数return
后面少了分号,在js引擎解析时,编译原理的知识可知,在词法分析会return
后面默认加上分号,因此,后面那个对象压根不执行,压根不搭理。因此啊,当return
时返回的就是undefined
。
面试5:函数表达式的做用域范围
<script>
console.log(!eval(function f() {})); //false
var y = 1;
if (function f(){}){
y += typeof f;
}
console.log(y); // 1undefined
</script>
复制代码
Code 1:
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//遍历加监听
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function () {
alert('第'+(i+1)+'个')
}
}
</script>
复制代码
输出结果:无论点击哪一个button
,都是输出“第4个”。由于for
循环一下就执行完了,但是btn.onclick
是要等到用户事件触发的,故这个时候i
是3
.永远输出“第4个”。 一些细节问题:在这个过程当中,产生了多少个i
?一个i
,i
是全局变量啊。 事件模型的处理: 当事件被触发时,该事件就会对此交互进行响应,从而将一个新的做业(回调函数)添加到做业队列中的尾部,这就是js关于异步编程最基本的形式。 事件能够很好的工做于简单的交互,但将多个分离的异步调用串联在一块儿就会很麻烦,由于你必需要追踪到每一个事件的事件对象(例如上面的btn
).此外你还要确保全部的事件处理程序都能在事件第一次触发以前被绑定完毕。例如,若btn
在onclick
被绑定前点击,那么就不会有任何的事情发生。所以,虽然在响应用户交互或相似的低频功能时,事件颇有用,但它面对更复杂的需求时仍然不够灵活。 因此,从这个例子不只仅是对遍历加监听/闭包等理解。从这里也能够说明事件对象和事件机制的问题。因此,在ES6中会有promise
和异步函数进行更多更复杂需求上的操做。
Code 2 经过对象.属性
保存i
:
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//遍历加监听
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
//将btn所对应的下标保存在btn上(解决方式1)
btn.index = i
btn.onclick = function () {
alert('第'+(this.index+1)+'个')
}
}
</script>
复制代码
这个时候就是咱们想要的结果,点哪一个i
,button
就输出第几个。
Code 3 经过ES6的块级做用域 let
:
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//遍历加监听
for (let i = 0,length=btns.length; i < length; i++) { //(解决方式二)
var btn = btns[i]
btn.onclick = function () {
alert('第'+(i+1)+'个')
}
}
</script>
复制代码
在ES6中引入块级做用域,使用let
便可。
Code 4 利用闭包解决
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button')
//利用闭包
for (var i = 0,length=btns.length; i < length; i++) { //这里的i是全局变量
(function (j) { //这里的j是局部变量
var btn = btns[j]
btn.onclick = function () {
alert('第'+(j+1)+'个') //这里的j是局部变量
}
})(i); //这里的i是全局变量
}
</script>
复制代码
for
循环里有两个函数,btn.click
这个匿名函数就是一个闭包。它访问了外部函数的变量。 产生几个闭包?3个闭包(外部函数执行几回就产生几个闭包)。每一个闭包都有变量j
,分别保存着j=0
,j=1
,j=2
的值。故能够实现这样的效果。以前之因此出问题,是由于都是用着全局变量的i,同一个i值。 闭包有没有被释放?没有,一直存在。咱们知道闭包释放,那就是让指向内部函数的引用变量为null
便可。可是此时btn.onclick
一直引用这内部函数(匿名函数),故其闭包不会被释放。 闭包应不该该被释放?不该该。由于一个页面的一个button
是要一直存在的,页面显示过程当中,button
要一直关联着这个闭包。才能让每点击button1
就alert
(第1个)这样的结果。不可能让button
点击了一次就失效吧。那么假设要释放这些闭包,那就让btn.onclick=null
便可。 闭包的做用?延长局部变量j
的生命周期。
1.如何产生闭包?
b
,而内部函数中没有引用b
,则不会产生闭包。2.闭包究竟是什么?
闭包是指有权访问另外一个函数做用域中的变量的函数。
能够理解为:
包含了那个局部变量的容器(不必定是对象,相似对象)
他被内部函数对象引用着
怎么判断闭包存在否?最终就是判断函数对象有没有被垃圾回收机制。
3.产生闭包的条件?
案例1:
function fn1 () {
var a = 2
var b = 'abc'
function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
console.log(a) //引用了外部函数变量,若里面没有引用任何的外部函数变量(函数)则不会产生闭包
}
// fn2() 内部函数能够不执行,也会产生闭包。只要执行了内部函数的定义就行。但如果函数表达式呢?
}
fn1(); //外部函数要执行哦,不然不会产生闭包
复制代码
案例2:
function fun1() {
var a = 3
var fun2 = function () {
console.log(a)
}
}
fun1()
//这样子经过函数表达式定义函数,若没有在里面调用内部函数,则不会产生闭包。
复制代码
案例3:
function fun1() {
var a = 3
var fun2 = function () {
console.log(a)
}
fun2()
}
fun1()
//这样子经过函数表达式定义函数,但在里面调用了内部函数,那么这个状况是能够产生闭包的。
复制代码
函数表达式不一样于函数声明。函数声明要求有名字,但函数表达式不须要。没有名字的函数表达式也叫作匿名函数(anonymous function),匿名函数有时候也叫拉姆达函数,匿名函数的 ·name· 属性是空字符串。
以上这些案例,只是辅助理解。并无实际上的应用,下面就来讲说闭包实际能够应用的地方。
案例1:将函数做为另外一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
复制代码
深刻理解: 问题一:有没有产生闭包? An:条件一,函数的嵌套。外部函数fn1
,内部函数fn2
,条件一知足;条件二,内部函数引用了外部函数的数据(变量/函数)。a
就是外部函数的数据变量。条件二知足。条件三,执行外部函数。var f = fn1()
其中的fn1()
是否是执行了,外部函数执行了。赋值给f
变量是由于外部函数fn1
在执行后返回一个函数,用全局变量f
来保存其地址值。条件三知足。综上所述,产生了闭包。
问题二:产生了几个闭包? 产生了一个闭包。咱们根据上一节的知识可知:执行函数定义就会产生闭包。那么执行函数定义是否是只要执行外部函数便可,由于外部函数一执行,就会有函数上下文对象,就会函数声明提早,也就是执行了函数定义。那么,这个时候执行了几回外部函数?是否是一次。执行了一次外部函数,也就是声明函数提早了一次,执行函数定义这个操做作了一次,故只产生了一个闭包。也可得出结论,外部函数执行几回,就产生几个闭包。跟内部函数执行几回没有关系(前提,在能够生成闭包的状况下)
问题三:调用内部函数为何能够读出a的最新值? 从结果能够知道,f()
,f()
是否是在调用了两次内部函数,从输出的结果看,a
每次输出最新值。这就能够知道,在执行内部函数的时候,a
并无消失。记住这点,这就是闭包的本质做用。
问题四:那么若是我如今在以上代码最后(分别输出3
,4
语句后面)继续加入
var h =fn1();
h();
f();
复制代码
这个时候会输出什么? An:h()
会输出3
. f()
会输出5
. 由于:var h = fn1()
又执行了一次,h接收返回值函数对象(内部函数),也就是在这个时候产生了新的一个闭包。当调用内部函数时,h()
,就会有新的函数上下文对象产生,a
值就会从初始值开始记录。当调用f()
时,这个时候仍是在上一个闭包的状态下,那个做用域并无消失,故还在原先的基础上改变a
值。
案例2. 将函数的实参传递给另外一个函数调用(★★★)
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('my name is ly', 2000)
复制代码
这个例子说明了,咱们要使用闭包不必定要return
出去。只要这个函数对象被引用着就行。return
的话那我再外面用变量接收一下就引用着了。可是我使用定时器,定时器模块式在浏览器分线程运行着的,定时器这个回调函数就是定时器模块保存管理着。
深刻理解: 问题一:有没有产生闭包? An:条件一,函数的嵌套。外部函数showDelay
,内部函数定时器的回调函数,条件一知足;条件二,内部函数引用了外部函数的数据(变量/函数)。msg
就是外部函数的数据变量,而在回调函数中用了。注意,time
不是哦,time
仍是在外部函数用的,在内部函数中并无用到外部函数的time变量。是由于msg
变量才知足条件二。条件三,执行外部函数。showDelay('my name is ly', 2000)
执行了,外部函数执行了。可是注意回调函数没有声明提高,故还要等2000ms
后触发进行调用回调函数。这个时候内部函数才执行。条件三知足(这个相似于函数表达式状况,若是是函数表达式,那么不只仅要外部函数要执行,内部函数表达式定义的函数也要有执行,只有这样才会出现闭包。若是是函数声明定义的函数,那么就会有函数执行上下文去建立,函数提高,故在执行函数定义的时候就会出现闭包)。综上所述,产生了闭包。
1.使用函数内部的局部变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期) 原本,局部变量的生命周期是否是函数开始执行到执行完毕,局部变量就自动销毁啦。可是,经过闭包能够延长局部变量的生命周期,函数内部的局部变量能够在函数执行完成后继续存活在内存中。那就是经过闭包,具体怎样的内部机制见下。
2.让函数外部能够操做(读写)到函数内部的数据(变量/函数) 原本,函数内部是能够经过做用域链去由内向外去找数据(变量/函数),是能够访问到函数外部的。可是反过来是不行的,函数外部能访问到内部的数据(变量/函数)吗?
function fun1() {
var a='hello world'
}
console.log(a); //报错 a is not defined
//函数外部不能访问函数内部的数据
复制代码
因此,函数外部不能访问函数内部的数据。可是,能够经过闭包去访问到函数内部的数据。具体的内部机制又是怎样的呢?见下。
//详解闭包的做用(重要)
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
function fn3() {
a--
console.log(a)
}
return fn3
}
var f = fn1()
f() // 1
f() // 0
复制代码
问题1. 函数fn1()
执行完后, 函数内部声明的局部变量是否还存在? An: 通常是不存在, 存在于闭包中的变量才可能存在。像fn2
fn3
变量就自动销毁了。由于函数内的局部变量的生命周期就是函数开始执行到执行完毕的过程。那像fn2
这个函数对象也会被垃圾回收机制回收,由于没有变量去引用(指向)fn2
函数对象。可是fn3
这个对象还在,根本缘由是由于语句 var f = fn1();
fn( )
执行完毕返回一个fn3
的地址值而且赋值给全局变量f,那么全局变量f就会指向他,因此,这个fn3
这个函数对象不会被垃圾回收机制回收。 但我在回答这个问题时,是说存在于闭包中的变量才可能存在。为何可能呢?由于若是我把语句var f = fn1();
改为fn1()
,这个时候仍是没有变量去引用,因此这时仍是会被回收的。见下图。
fn3
函数对象一直会有引用,闭包就会存在。这时我只要将
f=null
,这个时候
fn3
函数对象就没有被
f
引用,因此会被垃圾回收机制回收,故此时这个闭包死亡。
问题2:在函数外部能直接访问函数内部的局部变量吗? An: 不能, 但咱们能够经过闭包让外部操做它.
function fn1() {
//此时闭包就已经产生了(函数提高, 内部函数对象已经建立了)
var a = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
复制代码
闭包应用:
jQuery
)大量使用了闭包闭包的应用之一:定义JS模块
要具备特定功能的js文件
将全部的数据和功能都封装在一个函数内部(私有的)(函数内部会有做用域与做用域链的概念,函数内部的数据就是私有的,外部访问不到。)
只向外暴露一个包含n个方法的对象(暴露多个行为)或函数(暴露一个行为)
模块的使用者, 只须要经过模块暴露的对象调用方法来实现对应的功能
自定义JS模块一:
function myModule() {
//私有数据
var msg = 'Hello world';
//操做数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
//怎么使用?在html页面中
var module = myModule()
module.doSomething()
module.doOtherthing()
复制代码
自定义JS模块二:
(function () {
//私有数据
var msg = 'My atguigu'
//操做数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})();
//怎么使用?在html页面中
myModule2.doSomething()
myModule2.doOtherthing()
//这种自定义模块相对而言更好,由于不须要先调用外部函数,直接使用 myModule2.doSomething()更加方便。
复制代码
1.缺点
2.解决
<script type="text/javascript">
function fn1() {
var arr = new Array[100000]
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
//这里是有闭包的,arr一直没有释放,很占内存。
//如何解决?很简单。
f = null //让内部函数成为垃圾对象-->回收闭包
</script>
复制代码
理解:
做用:
写一个闭包程序
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();
f();
复制代码
闭包应用:
jQuery
)大量使用了闭包缺点:
f = null;
//让内部函数对象成为垃圾对象面试1:考察闭包
function foo(){
var m = 1;
return function (){
m++;
return m;
}
}
var f = foo(); //这会造成一个闭包 (调用一次外部函数)
var f1 = foo(); //这会造成一个闭包 (调用一次外部函数)
/*不一样闭包有不一样做用域。同一个闭包能够访问其最新的值。--这句话知识一个表面现象,结合上面的案例去发现深刻的步骤,这个过程是如何执行的?*/
console.log(f()); // 2
console.log(f1()); // 2
console.log(f()); // 3
console.log(f()); // 4
复制代码
面试2:闭包相关知识
<script>
function fun(n, o){
console.log(o); //实则就是输出闭包中的变量值,n是闭包引用的变量。延长的是n的变量生命周期。
return {
fun: function (m){
return fun(m, n);
}
}
}
/*注意以上代码段是有闭包的,return fun(m,n)中的n是用到了外部函数的变量n*/
//测试一:
var a = fun(0); // undefined
a.fun(1); // 0 执行这里实则是产生了新的闭包,但没有变量去指向这个内部函数产生的闭包故立刻就消失啦。
a.fun(2); // 0
a.fun(3); // 0
/*最后三行语句一直用的闭包是fun(0)产生的闭包*/
//测试二:
var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
/*产生了四个闭包,也就是外部函数fun(n,o)调用过4次。*/
// 测试三:
var c = fun(0).fun(1); // undefined 0
c.fun(2) // 1
c.fun(3) // 1
/*最后两行语句一直用的闭包是fun(0).fun(1)产生的闭包,故其语句*/
</script>
复制代码
面试3:写一个函数, 使下面的两种调用方式都正确
console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5
复制代码
答案:
<script>
function sum(){
if(arguments.length == 2){
return arguments[0] + arguments[1];
}else if(arguments.length == 1){
var first = arguments[0];
return function (a){
return first + a;
}
}
}
</script>
复制代码
此文档为吕涯原创,可任意转载,但请保留原连接,标明出处。 文章只在CSDN和掘金第一时间发布: CSDN主页:https://blog.csdn.net/LY_code 掘金主页:https://juejin.im/user/5b220d93e51d4558e03cb948 如有错误,及时提出,一块儿学习,共同进步。谢谢。 😝😝😝