写在开头: 准备自从更博以来每天更新一些新内容上去。就在前几天连续3天每天一篇文章之后收到消息,大概意思是取消博文发布,请发布和程序员相关,原创的东西。看到这个消息有点满脸闷逼啊,为啥子? 本来想:这是学习过程中的一些笔记,与大家共享,也提醒自己成长到了何种程度。但是乎,看到消息后,思前想后,想出了以下几点原因:1.写博客时未注明这些知识体系是自己学习笔记,在此,补上;第二:是我更新太快,被怀疑有水份。这也是停了两天不更新的原因。开头的最后,难关得过,继续更新。
首先从js初级部分引入,包括语言构成,变量、数据类型、函数(简单介绍)、面向对象(简单介绍)
1. JS 基础概要
1.1. 语言构成:
(1). 基础语法(ECMAScript=ES 1,2,3,3.1,5(IE9+),6:ES2015)/
(2). BOM:Brower Object Model 浏览器对象模型
(3). DOM(W3C):Document Object Model 文档对象模型
1.2. 变量
1.2.1. 变量声明
变量的声明:(var关键字) 会提升到当前作用域的顶部
1.2.2. 变量作用域
JS作用域:全局作用域、局部作用域(写一个函数就产生了一个局部作用域)
1.3. 数据类型
1.3.1. 数据类型的分类
javaScritp的数据类型有:数值类型、字符串类型、布尔类型、null、undefined、对象(数组、正则表达式、日期、函数)
其中:
(1). 基本数据类型:数值、字符串、布尔、null、undefined (值类型)
(2). 复杂(复合)数据类型:对象 (引用类型)
typeof 可以检测的数据类型有:
使用typeof可以检测数值、字符串、布尔、undefined、函数
无法检测函数以外的对象类型,以及null
1.3.2. 数据类型的转换:
Number("12ab"); //NaN
parseInt("12.1ab"); //12
parseFloat("12.1ab"); // 12.1
1.4. 运算符
1.4.1. 算术运算符:
+、 -、 *、 /、 %(求余、取模)
1.4.2. 逻辑运算符:
&&、 ||、 !
注意:
&&:看运算符左边的数是否为真值,如果为真值,返回右边,如果为假值,返回左边
||:看运算符左边的数是否为真值,如果为真值,返回左边,如果为假值,返回右边
1.4.3. 真假值
假值:空字符串""/数字0/null/undefined/false/NaN
1.5. 语句
循环语句:for while do...while for...in
1.6. 函数
1.6.1. 函数声明
(1). 声明式函数——>函数声明提前(会提升到当前作用域的顶部)
(2). 函数表达式(变量声明提前)
注意:
如果同时存在变量声明和函数声明,那么声明后的结果是一个函数
1.6.2. 函数参数和返回值
arguments获取实参的相关信息
函数的返回值由return 语句决定
1.7. 异常
异常:JS代码执行的时候出现的错误,出现错误之后,后面的代码无法执行
异常捕获:代码出现了异常之后,进行手动的捕捉
异常捕获的使用场景:一段代码可能发生错误,这个错误现在没时间解决,为了保证后面的代码可以成功执行,就进行异常捕获:try...catch
1.8. 面向对象初体验
(1). JS是一门基于对象的多泛式语言
可以使用面向过程进行开发:
比如:获取元素,绑定事件、设置样式、完成动画。。。。。。
(2). 可以使用面向对象的方式进行开发
面向(关注于)过程:基于函数,封装函数
面向对象:关注点变成了对象
对象的概念:数据集,功能集
1.9. 面向对象一个例子
1 //CEO:安排一个任务给CTO(7天),CTO又把任务给PM(5天),PM又把任务给我了(3天),我去开发这个页面 2 var ceo = { 3 assignTaskToCTO: function () { console.log("安排一个任务给CTO"); } 4 }; 5 var cto = { 6 assignTaskToPM: function () { console.log("安排一个任务给PM");} 7 }; 8 var pm = { 9 assignTaskToMe: function () { console.log("安排一个任务给我"); } 10 }; 11 var me = { 12 developWeb:function(){ console.log("我去开发这个页面"); } 13 }; 14 //开发一个页面 15 function deleveWeb(){ 16 ceo.assignTaskToCTO(); 17 cto.assignTaskToPM(); 18 pm.assignTaskToMe(); 19 me.developWeb(); 20 }
2. 构造函数(☆☆☆☆)
2.1. 为什么要有构造函数
一些对象具有相同的属性和方法(特征和行为),将他们抽象出一个同一个类型,在JS中就需要通过一个构造函数来创建这些对象,在构造函数内部设置对象的属性和方法
好处:一次封装,多次调用,可省略一些代码,也让代码更具有可读性。
2.2. 获取对象上的属性和方法
(1). 属性:
a. 实例.属性名;
b. 实例["属性名"];
(2).方法:
a. 实例.方法名;
b. 实例["方法名"];
2.3. 设置对象上的属性和方法
(1). 属性:
a. 实例.属性名=新的值;
b. 实例["属性名"]=新的值;
(2). 方法:
a. 实例.方法名=新的值;
b. 实例["方法名"]=新的值;
2.4. 删除原型对象中的say方法
delete
2.5. 构造函数和普通函数
构造函数和普通函数只在调用方式不同
(1). 当成普通函数来调用
a. 函数内部的this指向调用的对象(如果没有找到调用的对象,this指向window)
b. 函数的返回值由return语句决定,如果没有说明函数没有返回值(返回值是undefined)
(2). 当成了构造函数来调用会经历以下过程
a. 创建一个该构造函数的实例
b. 将构造函数内部的this的值指向该实例
c. 执行函数体
d. 默认的返回值:该实例
(3). 函数调用的4种方式 function fn(){}
a. 普通调用:fn();
b. 当成构造函数调用:new fn();
c. 被对象调用:o.fn();
d. 上下文模式:call/apply
(4). 构造函数的返回值
a. 构造函数没有手动添加返回值,返回构造函数的实例
b. 构造函数返回基本数据类型的值,返回的还是构造函数的实例
c. 构造函数返回对象类型的值,返回就是那个对象
3. 原型对象
原型对象:构造函数的prototype属性:随着实例化的次数增加,不同的对象他们拥有的say方法指向不同的内存,功能相同,造成了内存的浪费,为了解决内存,将这个方法放在某个对象(原型对象)中.
结论1:给构造函数的prototype属性(对象)添加一个方法,这个方法就可以被构造函数的实例所共享
推论1:构造函数的prototype属性(对象)上面的属性、方法都可以被构造函数的实例所共享
推论2:Student.prototype.constructor===s1.constructor
结论2:构造函数的实例有一个__proto__指向的是构造函数的prototype属性(原型对象) s1.__proto__===Student.prototype
总结:
a. 原型对象是构造函数的prototype属性
b. 构造函数的实例的__proto__属性指向原型对象
c. 原型对象有一个constructor属性指向构造函数本身
4. 对象的属性的读取与设置
查找一个对象上的是否存在某个属性的过程
a. 查找当前对象(s1)的内存中是否定义了该属性,找到就停止查找
b. 去当前对象的__proto__属性(原型对象)中去查找是否定义了该属性,找到就停止查找
c. 如果2中没找到,就去原型对象的原型对象中去查找是否定义了该属性
s1.__proto__.__proto__
......
n. 找到某个对象(是没有原型对象的:没有__proto__属性),如果这个对象中还没有,确定了无法获取该属性
5. 继承
5.0. 继承的概念
a继承自b——> a.__proto__===b
5.1. 扩展原型对象实现继承
构造函数有一个prototype属性(原型对象),通过给原型对象添加一个属性、方法,从而可以让构造函数的实例可以访问到,这种继承模型称之为:扩展原型对象实现继承。
1 function Person(){}; 2 Person.prototype.age = 20; 3 var p1 = new Person();
由于p1是Perosn的实例,所以p1是继承自Person构造函数的原型对象。
原型图如下:
5.2. 替换原型对象实现继承
为什么要用替换原型对象实现继承?
当我们需要给构造函数的原型对象中添加很多个属性、方法的时候,如果一直使用扩展原型对象,多写很多冗余(重复)的代码
如何实现替换原型对象实现继承?
重新给构造函数的prototype属性赋值,值是一个全新的对象,在这个对象中添加属性、方法;要注意一定要给这个对象添加constructor属性,值指向构造函数本身
原型图如下:
5.3. 混入继承
混入继承的使用场景:已知对象o,o2,需要将o中的功能(属性、方法)拷贝到o2中 jQuery.extend() 方法;
实现方式:
1 for (var key in o) {//key是o的属性名称,key是字符串类型的值 2 //属性的值: 3 var value=o[key]; 4 //设置o2中的同名属性 5 o2[key]=value; 6 }
将混入继承的模型封装成函数
1 /** 2 * 混入继承:将源对象中的属性和方法拷贝到目标对象中 3 * @param target 目标对象:接收数据的对象 4 * @param source 源对象:数据从哪个对象来 5 */ 6 function mixin(target,source){ 7 for (var key in source) { 8 var value=source[key]; 9 target[key]=value; 10 //相当于: 11 //target[key]=source[key]; 12 } 13 return target; 14 }
jQuery中的$.extend()实现原理就是混入继承
5.4. 原型+混入继承
例子
1 function Cat(){} 2 Cat.prototype.extend=function(source){ 3 //调用实现混入继承的函数往原型对象中添加属性、方法 4 mixin(Cat.prototype,source); 5 };
原型+混入继承在jQuery中也有很常见的应用:$.fn.extend()-->jQuery.prototype.extend()
5.5. 经典继承
实现的功能:已知一个对象o,需要创建一个新的对象(o2),这个新的对象继承自对象o
经典继承的使用场景:要创建一个对象(不需要关心构造函数)新对象需要继承自另一个指定的对象
1 //o2.__proto__===o; 将经典继承封装成一个函数: 2 function create(o){ 3 function F(){} 4 F.prototype=o; 5 return new F();//返回的就是F的实例 6 }
ES5(IE9以下版本不支持):Object.create()的实现原理就是源自于经典继承
在旧的浏览器中应用经典继承
1 //1、首先应该先检测浏览器是否支持Object.create() 2 if(typeof Object.create !=="function"){ 3 Object.create=function(o){ 4 function F(){}//一个任意的构造函数 5 F.prototype=o;//设置构造函数的原型对象 6 return new F();//返回的就是F的实例 7 }; 8 }
6. 原型链
6.0. 基本概念
JS由对象组成,一个对象就有它的原型对象(__proto__),原型对象也有它的原型对象,一直到原型链的顶端,这样构成了一个具有链条形状的结构,称之为原型链
__proto__该属性可以被修改,但是无法被删除
对象字面量的原型链图
构造函数创建对象的原型链图
数组对象的原型链图
一般来说,无论是对象字面量,还是构造函数创建的对象、内置对象,基本包装了类型的对象,2次原型查找(.__proto__)就可以找到
7. 一些属性方法
7.1. Object.prototype.toString()
1 var obj={}; 2 console.log(obj.toString());//"[object Object]" 3 console.log(Object.prototype.toString.call([]));//"[object Array]" 4 console.log(Object.prototype.toString.call(/abc/));//"[object RegExp]" 5 console.log(Object.prototype.toString.call(new Date()));//"[object Date]" 6 console.log(Object.prototype.toString.call(1));//"[object Number]" 7 //1-->转换为基本包装类型的对象 8 console.log(Object.prototype.toString.call("abc"));//"[object String]" 9 console.log(Object.prototype.toString.call(true));//"[object Boolean]" 10 console.log(Object.prototype.toString.call(function(){}));//"[object Function]" 11 function Cup(){} 12 var cup=new Cup(); 13 console.log(Object.prototype.toString.call(cup));//"[object Object]"
使用Object.prototype.toString.call()只能检测内置对象的类型——>$.type
7.2. Object.prototype.hasOwnProperty()
用来判断方法的参数(字符串类型)是否是对象的自有属性(属性定义在对象自身的内存中——>通过原型链找到的属性就不对了)
7.3. Object.prototype.isPrototypeOf()
表示当前对象是否是参数的原型对象(或在参数的原型链上)
7.4. Object.prototype.propertyIsEnumerable()
表示参数(字符串类型)是否是当前对象的可枚举(for...in遍历)属性
7.5. Object.defineProperty(); //ES5诞生的
1 //参数1:需要定义属性的对象 2 //参数2:属性的名称(字符串类型) 3 //参数3:属性的描述符(对象) 4 value; 5 enumerable; //添加了一个不可枚举属性 6 writable; //添加一个不可使用赋值运算符的属性 7 configurable; //值为true表示该属性可以被删除,值为false表示该属性不可被删除 8 get; //定义一个只读(只能获取值)属性 9 set; // 定义一个只写属性,仅仅设置set
如:
1 Object.defineProperty(o1,"length",{ 2 enumerable:false, 3 value:"170" 4 });
7.6. a instanceof b
表示a是否为b(函数)的实例
8. 函数
8.1. 函数的创建
1 function f(){} //new Function()//任何函数都是通过Function创建出来的(任何函数都是Function的实例) 2 var f1=new Function("var a=1;var b=2;console.log(a+b);");//创建一个无参无返回值的函数 3 var f2=new Function("num","console.log(num);");f2(10);//创建一个有一个参数,无返回值的函数 4 var f3=new Function("a","b","c","return a+b+c; "); //创建一个具有3个参数,有返回值的函数 5 var f5=new Function("o","function F(){} " +"F.prototype=o;" +"return new F();");//创建一个经典继承的函数
8.2. 函数的原型链
function f(){}
f();//f当成了普通函数来调用
new f();//f当成了构造函数来调用
一些结论:
a. 几乎所有函数都有prototype属性(Function.prototype这个函数上没有)
b. 所有对象中都有__proto__属性(Object.prototype该属性的值null)
——> 几乎所有函数都有prototype/__proto__属性
c. 函数都是Function的实例(函数是通过Function创建出来的对象)
——> 自定义函数、Function、Array、RegExp、String、Boolean、Number、Object
d. 几乎所有函数都是继承自:Function.prototype(除了Function.prototype)
——> 函数.__proto__===Function.prototype
——> Object.__proto__===Function.prototype
——> Function.__proto__===Function.prototype
e. Function.prototype.__proto__===Object.prototype
String.prototype.__proto__===Object.prototype
Array.prototype.__proto__===Object.prototype
Boolean.prototype.__proto__===Object.prototype
Number.prototype.__proto__===Object.prototype
RegExp.prototype.__proto__===Object.prototype
下面的结果是:
function fn(){}
a、console.log(fn.constructor===Function);//true //查找fn的内存——>查找fn.__proto__(Function.prototype)——>Function
b、console.log(fn.__proto__===_______);//true //Function.prototype
c、console.log(Object.__proto__===______);//true //Function.prototype
d、console.log(Function.prototype===______);//true //Function.__proto__——>fn.__proto__——>Object.__proto__
e、console.log(Object.constructor); //Function
f、console.log(fn.prototype.constructor); //fn
g、console.log(Function.prototype.__proto__.constructor); //Object
原型链完整图
9. 一个小栗子 ——
1 /*js*/ 2 /** 3 * Created by mhq on 2016/10/12. 4 */ 5 /*用来创建整个页面的对象的构造函数,有一个参数*/ 6 function SongManager(parameter) { 7 /*页面的初始化*/ 8 /*访问原型对象中的方法,this指向构造函数的实例*/ 9 this.init(parameter); /*方法调用函数的方式调用的*/ 10 } 11 /*页面的功能添加到对象的构造函数的原型对象中,以减少存储空间,优化代码*/ 12 /*替换原型对象的方法实现继承:用一个新的对象替换原型对象,在这个新对象中添加功能*/ 13 SongManager.prototype = { 14 /** 15 * 页面初始化 16 * @param parameter 用户在页面中传过来的数据 17 */ 18 init: function (parameter) { // this指的是构造函数的实例 19 /*页面渲染方法*/ 20 this.renderDom(parameter); 21 /*添加事件方法*/ 22 this.addEvents(); 23 }, 24 /** 25 * 渲染页面 26 * @param parameter 27 */ 28 renderDom: function (parameter) { 29 /*首先放到一个容器里*/ 30 var html = "<div class='song_manager_container'>" + 31 "<h3>歌曲管理</h3>" + 32 "<div>" + 33 "<label>歌曲名:</label>" + 34 "<input type='text' id='songName' /> " + 35 "<label>歌手名:</label>" + 36 "<input type='text' id='singerName' /> " + 37 "<input type='button' value='添加' id='addSongBtn' /> " + 38 "<input type='button' value='删除' id='deleteSongBtn'/>" + 39 "</div>" + 40 "<div class='song_manager_table'>" + 41 "<div class='first_column'>" + 42 "<span class='song_name'>歌曲名</span>" + 43 "<span class='song_name'>歌手名</span>" + 44 "<span class='song_name'>操作</span>" + 45 "</div>" + 46 "</div>" + 47 "</div>"; 48 /*将内容渲染到页面中*/ 49 /*首先将整个容器添加到页面对象的某个属性,假设为songManagerContainer,方便绑定事件等*/ 50 this.songManagerContainer = $(html); 51 $(parameter.parent).append(this.songManagerContainer); 52 var _this = this; 53 /*渲染已有的歌曲到页面中,添加歌曲*/ 54 $.each(parameter.data, function () { 55 /*这里的this指定是parameter.data中的对象*/ 56 _this.addSong(this); 57 }); 58 }, 59 /** 60 * 事件:点击按钮添加歌曲歌手;点击按钮删除歌曲 61 */ 62 addEvents: function () { 63 /*这里需要保持this,即构造函数的实例*/ 64 var _this = this; 65 /*这里的数据是假设传入的数据为对象*/ 66 this.songManagerContainer.on("click.addSongClick","#addSongBtn", function () { 67 var obj = {songName: _this.songManagerContainer.find("#songName").val(), singerName: _this.songManagerContainer.find("#singerName").val()}; 68 if (obj.songName && obj.singerName){ 69 /*调用添加歌曲方法,但是this此时指向了#addSongBtn这个dom元素*/ 70 _this.addSong(obj); 71 console.log(obj.songName); 72 _this.songManagerContainer.find("#songName").val(""); 73 _this.songManagerContainer.find("#singerName").val(""); 74 } 75 }).on("click.deleteSongClick",".deleteBtn", function (){ 76 console.log(this); 77 _this.deleteSong(this); 78 }); 79 }, 80 /** 81 * 添加歌曲、歌手功能 82 * @param obj 存储需要添加的歌曲,歌手信息对象 83 */ 84 addSong: function (obj) { 85 var html = "<div class='song_list'>" + 86 "<span>"+obj.songName+"</span>" + 87 "<span>"+obj.singerName+"</span>" + 88 "<span>" + 89 "<input type='button' class='deleteBtn' value='删除'/>" + 90 "</span>" + 91 "</div>"; 92 this.songManagerContainer.find(".song_manager_table").append(html); 93 }, 94 /** 95 * 删除歌曲所在行功能 96 * @param deleteBtn 参数为删除按钮 97 */ 98 deleteSong:function(deleteBtn){ 99 $(deleteBtn).parent().parent().remove(); 100 } 101 }; 102 /*less*/ 103 104 @charset "UTF-8"; 105 .disInlineB{ 106 display: inline-block; 107 } 108 .m_0{ 109 margin: 0; 110 } 111 .lh-40{ 112 line-height: 40px; 113 } 114 .height-40{ 115 height: 40px; 116 } 117 .w_700{ 118 700px; 119 } 120 .tac{ 121 text-align: center; 122 } 123 .song_manager_container { 124 margin: 40px auto; 125 .tac(); 126 .w_700(); 127 .song_manager_table{ 128 .w_700(); 129 .first_column{ 130 background: #0A7EC3; 131 margin: 10px 0; 132 .lh-40(); 133 .height-40(); 134 } 135 .song_list:nth-child(odd){ 136 background-color: #d0d0d0; 137 } 138 .song_list:nth-child(even){ 139 background-color: #0c6598; 140 } 141 span{ 142 200px; 143 .disInlineB(); 144 } 145 } 146 .song_list span{ 147 .disInlineB(); 148 .height-40(); 149 .lh-40(); 150 } 151 } 152 /*index.html*/ 153 154 <!DOCTYPE html> 155 <html lang="en"> 156 <head> 157 <meta charset="UTF-8"> 158 <title>面向对象案例-表格渲染</title> 159 <link rel="stylesheet" href="css/index.css"> 160 </head> 161 <body> 162 <script src="js/lib/jquery-1.12.4.js"></script> 163 <script src="js/app/index.js"></script> 164 <script> 165 /*参数:要给哪个元素添加功能*/ 166 var data = [{songName: "断点", singerName: "张敬轩"}, 167 {songName: "灰色空间", singerName: "罗志祥"}, 168 {songName: "我", singerName: "张国荣"}]; 169 var parameters = { 170 parent: document.body, 171 data: data 172 }; 173 /*将用户传入的多个参数放在一个对象中*/ 174 var songManger = new SongManager(parameters); 175 </script> 176 </body> 177 </html>
10. eval
函数是封装了一段可以重复执行的代码
eval方法的功能:执行一段JS代码(封装了代码)
在eval方法中没有作用域的概念(ES5严格模式有了独立的作用域)——>声明的变量都是全局变量,函数都是全局函数
eval和函数的比较
a. eval封装了一段代码(只能执行一次);函数封装了一段代码(可以重复执行)
b. eval中没有独立作用域的——>声明的变量和函数都是全局的; 函数中是有独立作用域 ——>函数内声明的变量和函数只能在当前函数内部所访问
使用场景:用来解析JSON数据(对于JSON的容错率很高)
在解析对象的时候,要用到{},而在JS语言中,{}既可以表示对象,又可以表示代码块,在eval中遇到{}就会把它当成语句来处理,为了解决这个问题,在解析(单个对象)的时候,需要在解析的同时给字符串添加前后小括号,在解析数组的时候,就不用添加
11. 静态属性和实例属性
(1). 给函数添加一个属性(静态属性——>函数对象自身的属性)
(2). 给某个构造函数的实例添加的属性:实例属性
12. 所有的函数对象都共有的一些静态属性
(1). name:获取函数的名称
(2). length:表示函数形参的个数
(3). caller:表示当前函数调用是在哪个函数内
13. arguments对象
arguments保存实参的相关信息
a. 获取第一个实参:arguments[0]
b. 获取实参的长度:arguments.length
arguments是一个伪数组(不是通过Array创建的),可以for循环像遍历数组一样来遍历这个对象
arguments.callee是获取当前函数本身,可以用来实现递归的功能;缺点:在ES5的严格模式中禁用
14. 严格模式
开启严格模式:"use strict";
(1). 严格模式中禁止给一个未声明的变量赋值:
(2). 严格模式中eval具有了独立作用域——>在eval中声明的变量和函数都是局部变量
(3). 严格模式中禁止使用arguments.callee进行递归调用
15. 递归:函数自己调用自己
15.1. 计算斐波那契数列第n项的值:1,1,2,3,5,8,13...
1 function fibonacci(n){ 2 if(n==1 || n==2) return 1; 3 return fibonacci(n-1)+fibonacci(n-2); 4 } 5 for (var i = 0; i < 10; i++) { console.log(fibonacci(i+1)); }
15.2. 递归计算阶乘
1 function factorial(n){ 2 if(n<0) return 0; //为了防止报错 3 if(n==0) return 1; //递归的结束条件:0的阶乘为1 4 return factorial(n-1)*n; 5 } 6 for (var i = 0; i < 10; i++) { console.log("数字:"+i); console.log(factorial(i));}
15.3. m的n次方
1 function pow(n, m) { 2 if (m === 0) return 1; 3 if (m < 0) return 1 / (pow(n, -(m + 1)) * n); 4 else if (m > 0) return pow(n, m - 1) * n; 5 } 6 for (var i = -2; i <= 0; i++) { 7 console.log(pow(2, i)); 8 }
15.4. 递归查找父元素
需求:要判断一个div是否在另一个div的下面
1 function find(child,parent){ 2 //实现思路:由子元素一级一级的查找父元素 3 //递归的结束条件:查到了文档的根节点、找到了父元素 4 if(child.parentNode===parent) return true; //说明已经找到了符合条件的父元素 5 if(child.parentNode===null) return false; //说明已经查找到了文档的根节点 6 return find(child.parentNode,parent); 7 //第1次执行find——>child.parentNode===parent 8 //第2次执行find——>child.parentNode.parentNode===parent 9 //第3次执行find——>child.parentNode.parentNode.parentNode===parent 10 } 11 console.log(find(d3,d10));//false 12 console.log(find(d3,d1));//true
在chrome浏览器中,如果是为了调试的方便,对于页面中的id元素,可以不获取,直接编写代码
16. 词法分析
16.1. JS程序执行过程
(1). 读取代码,主关注声明的部分:var
(2). 判断var后面的名字是否已经被标记,如果没有被标记过,就标记
(3). 读取完毕后,代码从上往下,从左往右依次执行
16.2. 词法作用域(作用域:变量可以使用到不能使用的范围)
词法作用域就是描述变量的访问范围:
(1). 在代码中只有函数可以限定作用范围,允许函数访问外部的变量
(2). 在函数内优先访问内部声明的变量,如果没有才会访问外部的
(3=. 所有变量的访问规则,按照预解析规则来访问
作用域链:每一个函数具有独立作用域,由于函数内可以套函数,所以在函数内部访问变量的时候,需要一级一级的往上查找该变量,这样就好像构成了一个链式结构,把它称之为作用域链
16.3. 闭包(闭包解决一些实际问题:利用函数可以跨作用域访问变量的特性)
16.4 闭包实现思路:外层函数,内层函数
通常设置外层函数的返回值就是内层函数
也可以让外层函数的返回值是一个对象(方法)
如果需要保存一个数据(外层函数的同一个变量),让内层函数调用多次,该变量的值都是共享的
如果需要保存多个数据(外层函数的同一个变量),让外层函数调用多次
17. 函数调用、this指向、返回值
一个函数最终产生什么样的结构,跟如何调用这个函数息息相关:函数的四种调用模式
17.1. 4种调用模式
(1). 第一种模式:函数调用模式,也就是写一个函数,然后调用一下
(2). 第二种模式:方法调用模式,也就是将函数成为对象的一个方法,然后通过对象来调用
(3). 第三种模式:构造函数调用模式,也就是将函数当成构造函数来调用
(4). 第四种调用模式:上下文调用模式:根据调用方式的不同可以产生不同的结果
实现方式:call/apply (apply和call的唯一区别是第二个参数是数组,将实参值一一传到数组中。fn.call(函数内部的this的值,实参1,实参2...))
17.2 不同调用模式中的this的值
(1). 函数调用模式中this指向:window
(2). 方法调用模式中this指向:调用的对象
(3). 构造函数调用模式中this指向:构造函数的实例
(4). 上下文调用模式中this指向:
a. 如果call方法的第一个参数是一个对象,则fn函数内部的this的值指向该对象
b. 如果call方法的第一个参数是一个字符串、数字、布尔值,则fn函数内部的this的值会转换为该类型所对应的基本包装类型的对象
c. 如果call方法的第一个参数是null,则fn函数内部的this的值是window——>就相当于是一次函数调用模式
17.3 调用模式中的返回值
(1). 函数调用模式中返回值:由return语句决定
(2). 方法调用模式中返回值:由return语句决定
(3). 构造函数调用模式中的返回值:
a. 如果构造函数没有手动设置返回值,那么会返回构造函数的实例
b. 如果手动给构造函数添加了返回值,有以下2种情况:
(a). 返回值是值类型:最终的返回值还是构造函数的实例
(b). 返回值是引用类型(对象):最终的返回值就是该对象
(4). 上下文调用模式中的返回值:由return语句决定
17.4 call/apply区别
1.相同点:
(1) 都是Function.prototype对象中定义的方法
(2) 第一个参数都是表示函数内部的this的值
2. 不同点:
如果需要给函数传递参数的时候:
利用call方法,将函数的参数从第二个参数开始依次排开
apply方法的第二个参数是一个数组对象,数组的第一个参数表示函数的第一个实参,依次以此类推
17.5 apply的一个漂亮的应用
1 var points = [ 2 { x: 110, y: 50}, { x: 130, y: 60 }, { x: 20, y: 70 }, { x: 60, y: 50 } 3 ]; 4 var maxX = Math.max.apply( null, points.map(function (v) { return v.x; }));
以上代码中借用Math对的max方法,利用arr.map()方法中返回的是数组这一特性得到了数组中对象的某个属性的最大值;
18. 借用构造函数实现继承
定义:子类构造函数(Student)借用父类构造函数(Person)来完成:给子类的实例添加属性
注意条件:由于要借用父类构造函数,所以父类构造函数的功能要对子类对象通用