最近在系统的学习 JS 深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助。如有错误请留言指正,tks。
了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些概念。
本文只用实例验证结果,并做简要说明,给大家增加些印象,因为单独一项拿出来都需要大篇幅讲解。
- function show(x) {
- console.log(typeof(x));// undefinedconsole.log(typeof(10));// numberconsole.log(typeof('abc'));// stringconsole.log(typeof(true));// boolean
- console.log(typeof(function() {}));//function
- console.log(typeof([1, 'a',true]));//objectconsole.log(typeof({
- a: 10,
- b: 20
- })); //objectconsole.log(typeof(null));//objectconsole.log(typeof(newNumber(10)));//object
- }
- show();
undefined, number, string, boolean 属于简单的值类型,不是对象;函数、数组、对象、null、new Number(10) 都是对象;判断一个变量是不是对象非常简单。值类型的类型判断用 typeof,引用类型的类型判断用 instanceof。
- var obj = {
- a: 10,
- b: 20
- };
- var arr = [5, 'x', true];
语法糖其实是一种 "快捷方式",上面的代码是一种缩写,完整的写法是:
- varobj =new Object();
- obj.a=10;
- obj.b=20;
- vararr=new Array();
- arr[0]=5;
- arr[1]='x';
- arr[2]=true;
方法一:
- varfn =function() {
- alert(100);
- };
- fn.a = 10;
- fn.b =function() {
- alert(123);
- };
- fn.c = {
- name: "典橙贸易",
- year: 2016
- };
fn 是函数,a/b/c 是 fn 创建的对象。
方法二:
- var obj = {
- a: 10,
- b: 20
- };
- var arr = [5, 'x', true];
- 其本质是:
- var obj = new Object();
- obj.a = 10;
- obj.b = 20;
- var arr = new Array();
- arr[0] = 5;
- arr[1] = 'x';
- arr[2] = true;
- 而console.log(typeof(Object)); // function
- console.log(typeof(Array)); // function
只有构造函数才有 prototype 属性。通常我们自定义的函数都属于构造函数,所以都有此属性。JS 运行时环境内置的函数有些不是构造函数,比如 alert 和 Math.sqrt 等,就没有此属性。注:构造函数是指有一个内部属性 [[Construct]],通过 new 可以创建对象的那些函数。
每个函数 function 都有一个 prototype,即原型。每个对象都有一个__proto__,即隐式原型。只有构造函数才有 prototype 属性。对象的__proto__指向的是创建它的函数的 prototype。函数的 prototype 都是被 Object 创建的,每个自定义函数创建之初,都会有一个 prototype(它是如何被创建的,不得而知!)。例如:
- function Foo(){};
- varfoo =new Foo();
- Foo.__proto__ === Function.prototype// 任何函数都是由Function创建的,所以任何函数的__proto__都指向Function的prototypefoo.__proto__ === Foo.prototype// 对象的__proto__指向的是创建它的函数的prototypeFoo.prototype.__proto__ === Object.prototype// 如果是自定义的函数,它的prototype.__proto__ 指向 Object.prototype,因为自定义函数的prototype本质上就是和语法糖 var obj = {} 是一样的,都是被Object创建。Object.prototype.__proto__ ===null // Object.prototype也是个对象,它的__proto__是null(这是个特例,切记切记)
- foo.prototype == undefined//只有函数才有prototype属性,foo是对象Object.prototype === (newObject()).__proto__// 如上所述,Object本身是一个函数,(new Object())是对象Function.prototype.__proto__ === Object.prototype// Function是一个函数,它的prototype是被Object创建,所以它的Prototype.__proto__指向创建Object.prototypeFunction.__proto__ === Function.prototype//Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的Object.__proto__ === Function.prototype//Object是个函数,也是对象,它的__proto__指向的是创建它的函数的prototype
参考:
http://www.cnblogs.com/wangfupeng1988/p/3978131.htmlhttp://www.cnblogs.com/wangfupeng1988/p/3979290.html
Instanceof 运算符的第一个变量是一个对象,暂时称为 A;第二个变量一般是一个函数,暂时称为 B。Instanceof 的判断规则是:沿着 A 的__proto__这条线来找,如果 B 的 prototype 在这条线上,那么就返回 true,否则返回 false。
如:
- function Foo() {}
- var f1 = new Foo();
- console.log(f1 instanceof Foo); // true
- console.log(f1 instanceof Object); // true
分析方法:f1 的__proto__路线 A:
- (1) f1.__proto__ === Foo.prototype
- (2) Foo.prototype.__proto__ === Object.prototype
- (3) Object.prototype.__proto__ ===null //到终点
结论:f1 instanceof Foo :A 线路 (1) 中出现了 Foo.prototypef1 instanceof Object :A 线路 (2) 中出现了 Object.prototype
参考:
http://www.cnblogs.com/wangfupeng1988/p/3979533.html
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。例如:
- function Foo() {}
- var f1 = new Foo();
- f1.a = 2;
- f1.b = 3;
- Foo.prototype.b = 6;
- Foo.prototype.c = 7;
- console.log(f1.a); // 2,a是f1的基本属性,直接访问没问题
- console.log(f1.c); // 7,f1的基本属性中没有c,沿着原型链向上,f1.__proto__是Foo.prototype,而Foo.prototype有c,可以访问。
- console.log(f1.b); // 3,b是f1的基本属性,直接访问没问题。可Foo.prototype还有一个b,但是不会显示,这种情况被称为"属性遮蔽"。
判断属性是基本属性(也叫实例属性)还是原型链上的属性(也叫原型属性)使用 hasOwnProperty,一般在 for…in.. 循环中。for…in… 会输出实例属性和原型属性。属性列表参考实例属性 vs 原型的属性 vs 静态属性如:
- var item;
- for(itemin f1){
- console.log(item); // a,b,c
- }
- for(itemin f1){
- f1.hasOwnProperty(item) && console.log(item);// a,b}
在原型链的基础上,很容易理解继承。例如在原型链的实例中,f1.hasOwnProperty 就是继承自 Object.prototype。过程如下:
由于所有的对象的原型链都会找到 Object.prototype,因此所有的对象都会有 Object.prototype 的方法。这就是所谓的 "继承"。另外,每个函数都有的 call,apply 方法,和 length,arguments,caller 等属性也都是继承下来的。
定义 1:js 在执行一段代码之前,会声明提前,赋值或赋初始值(即 undefined)。所以同一代码段中,可以先访问,再定义,这就是 "执行上下文"。定义 2:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用 undefined 占个空。
分 4 种情况:
第 1 种情况:变量、函数表达式——只提前声明,不提前赋值,默认赋值为 undefined;
- console.log(a);// undefined
- vara=10;
- console.log(fn1); // undefined
- varfn1 =function(){};
第 2 种情况:this 关键字——提前声明并赋值
- console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
第 3 种情况:函数声明——提前声明并赋值
- console.log(fn2);// function fn2(){}
- functionfn2(){};
第 4 种情况:函数内部会创建自己独有的上下文环境
函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。另外,每个函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。如:
- vara = 10;
- function fn() {
- console.log(a); // a是自由变量,函数创建时,就确定了a要取值的作用域
- }
- function bar(f) {
- vara = 20;
- f(); // 10,到创建fn的地方找变量a的作用域,而不是调用它的当前作用域。
- }
- bar(fn);
如【执行上下文 / 执行上下文环境】所述,js 代码中,会有无数的函数调用和嵌套调用,会产生无数的上下文环境,这么多上下文环境如何管理,以及如何销毁而释放内存呢?这就需要【执行上下文栈】的参与了。
执行上下文栈:执行全局代码时,会产生一个全局上下文环境,并将该上下文环境压入栈,每次调用函数都又会产生执行上下文环境,并将该函数上下文环境也压入栈。当函数调用完成时,该函数上下文环境出栈,并消除该函数上下文环境以及其中的数据,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程。如:
- vara = 10,// 1. 进入全局上下文环境
- fn,
- bar =function(x) {
- varb = 5;
- fn(x + b);// 3. 进入fn函数上下文环境,并入栈,设为活动状态。该函数执行完毕后,bar函数上下文出栈,并及时销毁,释放内存
- };
- fn =function(y) {
- varc = 5;
- console.log(y + c);
- }
- bar(10);// 2. 进入bar函数上下文环境,并入栈,设为活动状态。该函数执行完毕后,bar函数上下文出栈,并及时销毁,释放内存
参考:http://www.cnblogs.com/wangfupeng1988/p/3989357.html
this 在执行上下文中有着非常重要的作用,这里应该说一下。
了解 this,需要记住一点:在函数中 this 到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。
大致分为以下 4 种情况:
情况 1:构造函数,this 就代表 new 出来的对象。
- function Foo() {
- this.name = '典橙贸易';
- this.year = 2016;
- console.log(this); // Foo {name: "典橙贸易", year: 2016}
- }
- var f1 = new Foo(); // 所谓构造函数就是用来new对象的函数。
- 另外,如果在原型链中使用this(Foo.prototype中使用this),this仍然代表new出来的对象。
- function Foo(){
- this.name = '典橙贸易2016';
- }
- Foo.prototype.getName =function(){
- console.log(this);// Foo {name: "典橙贸易2016"}
- }
- varf1 =new Foo();
- f1.getName();
情况 2:函数作为对象的一个属性, 并且被对象直接调用时,this 指向该对象。
- varobj = {
- name:'典橙贸易',
- fn:function(){
- console.log(this);// Object {name: "典橙贸易"}console.log(this.name);// 典橙贸易
- }
- }
- obj.fn(); // 直接调用
- // 如果不是直接调用,this就指向window
- varobj = {
- name: '典橙贸易',
- fn: function() {
- console.log(this);// Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}console.log(this.name);// undefined
- }
- }
- varfn1 = obj.fn;
- fn1();
情况 3:函数用 call 或者 apply 调用,this 就是传入的对象的值
- var obj = {
- name: '典橙贸易'
- }
- var fn = function() {
- console.log(this); // Object {name: "典橙贸易"}
- console.log(this.name); // 典橙贸易
- }
- fn.call(obj);
- // 或者
- // fn.apply(obj);
情况 4:全局 & 调用普通函数,this 是 window
全局:
- console.log(this===window);// true
- 调用普通函数:
- varname ="典橙贸易";
- varfn =function() {
- console.log(this);// Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}console.log(this.name);// 典橙贸易
- }
- fn();
注意:
- varobj = {
- name: '典橙贸易',
- fn: function() {
- function f() {
- console.log(this);// Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}console.log(this.name);// undefined
- }
- f(); // 函数f虽然是在obj.fn内部定义的,但是它仍然是一个普通的函数,this仍然指向window。
- }
- }
- obj.fn();
知识点 1:javascript 中没有块级作用域。
- vari = 2;
- if(i>1){
- varname ='典橙贸易';
- }
- console.log(name); // 典橙贸易for循环的{}也是类似。
知识点 2:javascript 除了全局作用域之外,只有函数可以创建自己的作用域,称为函数作用域。
如经典的问题:
- <ul id="list">
- <li>
- we
- </li>
- <li>
- sdf
- </li>
- <li>
- cx
- </li>
- <li>
- h
- </li>
- <li>
- z
- </li>
- </ul>
- varlist = document.getElementById('list');
- vare = list.getElementsByTagName('li');
- vari = 0;
- 错误写法:
- for(; i < e.length; i++) {
- e[i].onclick =function() {
- console.log(i);
- }
- }
- 正确写法:
- for(; i < e.length; i++) {
- (function(a) {
- e[i].onclick =function() {
- console.log(a);
- }
- })(i);
- }
- 等效于:
- for(; i < 5; i++) {
- varFn =function(a) {
- e[i].onclick =function() {
- console.log(a);
- }
- }
- Fn(i);
- }
错误的原因:e[i].onclick 每次循环,只是赋值给 onclick,而 for 循环是没有块级作用域的,所以 i 的值会不断累加,直到最大值 5,故每次循环都会输出 5。正确的原因:由于函数可以创建自己的作用域,而且各个作用域间不会相互影响,所以每次循环 Fn(i) 都会创建一个特有的函数作用域提供给相应的 onclick,而每个作用于中的 a 变量也不会相互影响。
知识点 3:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
- {
- vara = 1,
- b = 2;
- function fn1() {
- vara = 100,
- b = 200;
- function fn2() {
- vara = 1000,
- b = 2000;
- }
- }
- }
三个作用域下都声明了 "a 和 b" 变量,但是他们不会有冲突。各自的作用域下,用各自的 "a 和 b"。
在 A 作用域中使用的变量 x,却没有在 A 作用域中声明(即在其他作用域中声明的),对于 A 作用域来说,x 就是一个自由变量。如:
- varx=1;
- function fn(){
- varb=2;
- console.log(x+b);// 这里的x就是自由变量}
自由变量取值规则:要到创建 自由变量所在函数 的那个作用域中取值–是【创建】,而不是【调用】,也不是【父作用域】。
如:
- varx = 10;
- function fn() {
- console.log(x); // fn创建了自由的函数作用域,所以无论什么地方调用它,都会输出10
- }
- function show(f) {
- varx = 20;
- (function() {
- console.log(x); // 要到x所在的匿名函数的作用域中找x,故输出20f();// 要到创建fn函数的作用域中找x,故这里输出10,而不是20
- })();
- }
- show(fn);
在自由变量的基础上理解作用域链更加容易。在作用域 Fn 中使用一个变量 A,如果 Fn 中没有定义 A, 则到创建 Fn 的那个作用域 Fm 中找,如果仍没有找到,则继续到创建 Fm 的作用域中找…… 依次类推,直至找到变量 A 或者到全局作用域中仍未找到 为止,这种跨越多个作用域的线路,就叫 "作用域链"。代码如下:
- {
- // var A = 4; // 第4步,这是全局作用域,找到则返回4,如果到这里仍未找到,就结束,报错"A is not defined"
- function Fw() {
- // var A = 3; // 第3步,在作用域Fw中找,找到则返回3,否则到创建Fw的全局作用域中找
- function Fm() {
- // var A = 2; // 第2步,在作用域Fm中找,找到则返回2,否则到创建Fm的Fw中找
- function Fn() {
- // var A = 1; // 第1步,在当前作用域找,找到则返回1,否则到创建Fn的Fm中找
- console.log(A);
- }
- return Fn();
- }
- return Fm();
- }
- Fw();
- }
注意:
这里说的创建 Fn 的那个作用域,而不是调用 Fn 的那个作用域,也不是 "父作用域"。详情参考【自由变量及其取值规则 > 自由变量取值规则】中的实例。
要想理解闭包,前面的【作用域、自由变量、作用域链】三部分是基础。闭包的两种形式:函数作为返回值、函数作为参数被传递。
第一,函数作为返回值
- function fn() {
- var max = 10;
- return function bar(x) {
- if (x > max) { // max是自由变量,取值规则,参考【自由变量及其取值规则】
- console.log(x);
- }
- }
- }
- var f1 = fn();
- f1(15); // 15
第二,函数作为参数被传递
- varmax = 10,
- fn =function(x) {
- if(x > max) {// max是自由变量,取值规则,参考【自由变量及其取值规则】
- console.log(x);
- }
- };
- (function(f) {
- varmax = 100;
- f(15);// 15})(fn)
另外,在【执行上下文栈】中说到,每个函数执行完毕,都会销毁其函数上下文环境,并清空数据。但是闭包函数执行完后,上下文环境不会被销毁。因为闭包中函数会返回或者作为参数被传递,在其他地方会被用到,一旦销毁,调用闭包的地方就无法再使用了。所以闭包会增加内容开销。
参考:
http://www.cnblogs.com/wangfupeng1988/p/3994065.html
http://blog.csdn.net/yueguanghaidao/article/details/9568071
http://blog.csdn.net/lmj623565791/article/details/25076713
来源: http://www.cnblogs.com/yinluhui0229/p/6700888.html