走在前端的大道上css
本篇是 一篇文章带你理解原型和原型链 、一篇文章带你彻底理解this的查漏补缺,会不断丰富提炼总结更新。html
原型链 是针对构造函数的,好比我先建立了一个函数,而后经过一个变量new了这个函数,那么这个被new出来的函数就会继承建立出来的那个函数的属性,而后若是我访问new出来的这个函数的某个属性,可是我并无在这个new出来的函数中定义这个变量,那么它就会往上(向建立出它的函数中)查找,这个查找的过程就叫作原型链。前端
Object ==> 构造函数1 ==> 构造函数2面试
就和css中的继承同样,若是自身没有定义就会继承父元素的样式。segmentfault
function a(){}; a.prototype.name = "追梦子"; var b = new a(); console.log(b.name); //追梦子
做用域 是针对变量的,好比咱们建立了一个函数,函数里面又包含了一个函数,那么如今就有三个做用域数组
全局做用域==>函数1做用域==>函数2做用域app
做用域的特色:先在本身的变量范围中查找,若是找不到,就会沿着做用域往上找。函数
如:this
var a = 1; function b(){ var a = 2; function c(){ var a = 3; console.log(a); } c(); } b();
最后打印出来的是3,由于执行函数c()的时候它在本身的范围内找到了变量a因此就不会越上继续查找,若是在函数c()中没有找到则会继续向上找,一直会找到全局变量a,这个查找的过程就叫做用域链。spa
不知道你有没有疑问,函数c为何能够在函数b中查找变量a,由于函数c是在函数b中建立的,也就是说函数c的做用域包括了函数b的做用域,固然也包括了全局做用域,可是函数b不能向函数c中查找变量,由于做用域只会向上查找。
console.log(a); // Uncaught ReferenceError: a is not defined // 由于没有定义a因此报错了。
var a = 52; console.log(a); //52 // 有定义a,而且给a赋值了52因此打印a就是52。
console.log(a); //undefined var a = 52;
虽然有定义a可是打印却在变量a的前面,那为何不是报错而是打印出来的是undefined?由于在js执行代码以前,js会先获取到全部的变量而且把这些变量放置到js代码的顶部。(简称变量声明提早)
咱们给赋值给a的52到哪去了。虽然我前面说了js会事先获取全部的变量而且将这些变量放置到顶部,可是 变量的赋值并不会事先执行 ,也就是说,在哪声明的变量,这个变量的赋值就在哪执行。
实际上,上面的代码是这样执行的:
var a; console.log(a); //undefined a=52;
console.log(a); function a(){ this.user = "追梦子"; } //为何,能够事先就打印出函数a呢?
由于 函数的赋值在函数声明的时候 就已经赋值了,结合上面我说的变量提早,那是否是就能够理解这句话了?
function a(){ this.user = "追梦子"; } console.log(a); //正常
a(); //Uncaught TypeError: a is not a function var a = function(){ console.log(52); } //为何如今又不行了?
由于如今的函数已经赋值给了变量a,如今 它的执行过程和变量同样 了,咱们一般把这种函数赋值给变量的形式叫作 函数表达式。
var a = function(){ console.log(52); } a(); //52 //正常
if(false){ var a = 1; } console.log(a); //undefined
之因此没有报错而是输出了undefined是由于 变量存在预解析 的状况,又由于 js没有块级做用域,因此最后代码就成了这样
var a; if(false){ a = 1; } console.log(a);
总结:
函数分为:函数声明和函数表达式。
函数声明
function a(){ alert("追梦子博客"); }
函数表达式
var a = function(){ alert("追梦子"); }
看似两段相同的语句,它们的执行顺序却大相径庭,函数声明时的赋值行为是在函数建立的时候进行的,而函数表达式的赋值行为是在执行这句变量时进行的(由于它已经赋值给了变量因此我这里把它称为变量)。
不论是变量仍是函数都会存在变量声明提早。
来看看几题有意思的js例题,加以理解
var a = 1; function b(){ console.log(a); //undefined var a = 5; } b();
为何打印的是undefined?
咱们先来看看它的解析过程:
var a = 1; function b(){ var a console.log(a); //undefined a = 5; } b();
咱们一块儿来看看另一题比较有难度的js面试题:
var a = 1; function b() { a = 120; return; function a() {} } b(); alert(a); //1;
若是你看了上面一题我相信你应该有种不知所措的感受,这里如今为何又是1了呢?
我把执行过程的代码写出来我相信你就懂了。
var a = 1; function b() { var a; a = 120; return; function a() {} } b(); alert(a);
若是你正在js的进阶阶段确定更闷了,你确定会想咱们不是写return了吗?return后面的代码不是不执行吗?为何这里却影响了这段代码?
虽然return后面的代码不会被执行,可是在js预解析的时候(变量提高的时候)仍是会把return后面的变量提早,因此咱们这段代码 由于变量提早因此函数里面的变量a就成了局部变量,由于函数外部是访问不了函数内部的变量因此就输出了1。
另外提两点,函数的arguments和函数名都是直接赋值的,也就是在这个函数解析的时候就会进行赋值。
什么是自由变量?
如我在全局中定义了一个变量a,而后我在函数中使用了这个a,这个a就能够称之为自由变量,能够这样理解,凡是跨了本身的做用域的变量都叫 自由变量。
var a = "追梦子"; function b(){ console.log(a); //追梦子 } b();
上面的这段代码中的变量a就是一个自由变量,由于在函数b执行到console.log(a)的时候,发如今函数中找不到变量a,因而就往上一层中找,最后找到了全局变量a。
做用域的进阶
在我讲做用域链的时候说过若是有一个全局变量a,以及函数中也有一个变量a,那么只会做用函数中的那个变量a,都是有一种状况就显得比较复杂一些,咱们一块儿来看看这段代码。
var aa = 22; function a(){ console.log(aa); } function b(fn){ var aa = 11; fn(); } b(a); //22
最后打印的不是11而是22,为何会这样呢?一块儿来分析一下这段代码。
假如咱们的代码是这样的
var aa = 22; function a(){ console.log(aa); }
打印出的是22,我想你们应该没有意见,可是有一点我必定要提,那就是 在建立这个函数的时候,这个函数的做用域就已经决定了,而是否是在调用的时候,这句话至管重要。
分析一下过程,首先咱们建立了一个全局变量aa
var aa = 22;
接着咱们建立了一个函数a
function a(){ console.log(aa); }
这时js解析这个函数的时候,就已经决定了这个函数a的做用域,既若是在函数a中找不到变量aa那就会到全局变量中找,若是找到了就返回这个aa,若是找不到就报错。
接着咱们又建立了一个函数b
function b(fn){ var aa = 11; fn(); }
在函数b中咱们定义了又从新定义了这个变量aa,虽然咱们这个时候从新定义了变量aa,可是由于函数a的做用域在建立的时候已经决定了,因此在函数b中建立的那个变量aa以及和函数a里面的那个变量aa没有关系了。
function b(fn){ var aa = 11; fn(); } b(a);
咱们把函数a传到了函数b中,而且当作函数b的形参,接着咱们执行了这个被传进去的函数a,最后打印出来的就是22。
在建立这个函数的时候,这个函数的做用域就已经决定了,而是否是在调用的时候
。
笔者注: 看到这句话是否是似曾相识?this的指向在函数定义的时候是肯定不了的,只有函数执行的时候才能肯定this到底指向谁,实际上this的最终指向的是那个调用它的对象
一个是做用域,一个是上下文
举个例子回顾对比一下
box.onclick = function(){ function fn(){ alert(this); } fn(); };
咱们本来觉得这里面的this指向的是box,然而倒是Window。通常咱们这样解决,将this保存下来:
box.onclick = function(){ var _this = this; function fn(){ alert(_this); } fn(); };
还有一些状况,有时咱们想让伪数组也可以调用数组的一些方法,这时call、apply、bind就派上用场了。
咱们先来解决第一个问题修复this指向。
box.onclick = function(){ function fn(){ alert(this); } fn(); };
改为以下:
box.onclick = function(){ function fn(){ console.log(this); } fn.call(this); };
很神奇吧,call的做用就是改变this的指向的,第一个传的是一个对象,就是你要借用的那个对象。
fn.call(this);
这里的意思是 让this去调用fn这个函数,这里的this是box,这个没有意见吧?box调用fn,这句话很是重要,咱们知道 this始终指向一个对象
,恰好box就是一个对象。那么fn里面的this就是box。
能够简写的,好比:
box.onclick = function(){ var fn = function(){ console.log(this); //box }.call(this); };
或者这样:
box.onclick = function(){ (function(){ console.log(this); }.call(this)); //box };
又或者这样:
var objName = {name:'JS2016'}; var obj = { name:'0 _ 0', sayHello:function(){ console.log(this.name); }.bind(objName) }; obj.sayHello();//JS2016
call和apply、bind可是用来改变this的指向的,但也有一些小小的差异。下面咱们来看看它们的差异在哪。
call和apply、bind可是用来改变this的指向的,但也有一些小小的差异。下面咱们来看看它们的差异在哪。
function fn(a,b,c,d){ console.log(a,b,c,d); } //call fn.call(null,1,2,3); //apply fn.apply(null,[1,2,3]); //bind var f = fn.bind(null,1,2,3); f(4);
结果以下:
1 2 3 undefined 1 2 3 undefined 1 2 3 4
前面说过第一个参数传的是一个你要借用的对象,但这么咱们不须要,全部就传了一个null,固然你也能够传其余的,反正在这里没有用到,除了第一个参数后面的参数将做为实际参数传入到函数中。
call就是挨个传值,apply传一个数组,bind也是挨个传值,call和apply会直接执行这个函数,而bind并不会而是将绑定好的this从新返回一个新函数,何时调用由你本身决定。
var objName = {name:'JS2016'}; var obj = { name:'0 _ 0', sayHello:function(){ console.log(this.name); }.bind(objName) }; obj.sayHello();//JS2016
这里也就是为何我要用bind的缘由,若是 用call的话就会报错了。本身想一想这个sayHello在obj都已经执行完了,就根本没有sayHello这个函数了。
这几个方法使用的好的话能够帮你解决很多问题好比:
正常状况下Math.max只能这样用
Math.max(10,6)
但若是你想传一个数组的话你能够用apply
var arr = [1,2,30,4,5]; console.log(Math.max.apply(null,arr));
又或者你想让伪数组调用数组的方法
function fn(){ [].push.call(arguments,3); console.log(arguments); //[1, 2, 3] } fn(1,2);
再者:
var arr = ['aaabc']; console.log(''.indexOf.call(arr,'b')); //3
参考文章:
1.什么是做用域链,什么是原型链,它们的区别,在js中它们具体指什么?
2.js中的执行上下文,菜鸟入门基础
3.JS中call、apply、bind使用指南,带部分原理。
3.理解js中的自由变量以及做用域的进阶