1.原型深入 #

回顾:Object为对象类,所有的对象数据类型都是它的实例
同理,Function为函数类,所有的函数数据类型都是它的实例
同时,Function的原型上有call,apply,bind...方法

1.1 Function类和Object类的关系 #

Object是Function的实例,Function.prototype.__proto__是Object的实例
Function.prototype.__proto__指向Object.prototype

function Fn(){
  this.x = 100;
}
console.dir(Fn)

函数具有一些私有属性,如:
length:形参的个数;
name:函数名;
prototype:类的原型,在原型上定义的方法,都是当前Fn类实例的公有方法;
__proto__:把函数当做一个普通对象,指向Function的原型.

Fn.__proto__.__proto__也指向Object
所有的函数都是函数类的一个实例,也作为一个实例的存在,实例就是对象数据类型的

1.2 函数的多面性 #

一个函数存在了多面性:
1.本身是一个普通函数,执行时形成私有作用域(闭包),形参赋值、预解释、代码执行、栈内存释放;
2.本身是一个类,有自己的实例,也有prototype属性是自己的原型,它的实例都可以指向原型;
3.本身是一个普通对象,作为对象可以有自己的私有属性,也可以通过__proto__找到Function.prototype(函数的三种角色)
但是三者之间没有必然关系

function Fn(){
    var num = 500;
    this.x = 100;
}  //作为函数
Fn.prototype.getX = function(){
    console.log(this.x);
}  
Fn.aaa = 1000;  //作为对象
var f = new Fn; //作为类
console.log(f.num);
console.log(f.aaa);
var res = Fn();
console.log(res);
console.log(Fn.aaa);

Function.prototype是函数数据类型的值,但是相关操作和之前的对象一样
叫做Empty/anonymous(匿名)

console.log(Function.prototype);
//function(){}

Function类的原型为一个空函数
原型链

2.call方法 #

2.1 call语法 #

语法:call([thisObj[,arg1[, arg2[,[,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

2.2 call原理 #

举例模仿call原理:

var arr = [12,13,14,15];
Array.prototype.slice = function(context){
  //1.将函数中的this变为context
  //2.this方法执行
  this();
}
//arr.slice  arr实例通过原型链查找机制找到位于Array.prototype上的slice方法
//arr.slice();  让找到的slice方法执行,在执行的过程中对arr进行了截取操作

call()改变this关键字

Function.prototype.call = function(){}
var obj = {name:"Amy"};
function fn(){
  console.log(this);
}
fn();
fn.call(obj);

call:首先让原型上的call方法执行,在执行call方法时让fn的this变为第一个参数obj,然后再把fn执行

function f1(){
  console.log(1);
}
function f2(){
  console.log(2);
}
f1.call(f2); //1
f1.call.call(f2);  //call方法的this为f1.call
f1.call.call.call.call(f2);

f1通过原型链找到的是Function.prototype的call方法,然后再让call方法找到Function.prototype 因为本身的值为一个函数,所以同样可以找到Function原型,在第二次找到call的时候让方法执行,方法中的this为f1.call,首先让这个方法中的this变为f2,再让f2执行

f1.call只是起到了查找的作用

2.3 call和apply的区别 #

apply和call的方法作用是一模一样的,都是用来改变方法的this关键字,并且把方法执行; 在严格模式和非严格模式下下apply和call调用null,undefined的情况也是一样的

var obj = {name:"Amy"}
function fn(num1,num2){
  console.log(num1+num2);
  console.log(this);
}
fn.call(obj,100,200);
fn.call(100,200); //NaN,undefined
fn.apply(obj,[100,200]);

call是一个个传递值的;apply是传递两个值,将函数参数统一放到数组中进行操作,也相当于一个个赋值

function fn(){
  console.log(this);
}
//非严格模式下
fn.call();  //this->window
fn.call(null);  //this->window
fn.call(undefined);  //this->window

"use strict";
//严格模式
fn.call();  //this->undefined
fn.call(null);  //this->null
fn.call(undefined);  //this->undefined

严格模式下自执行函数的this永远是undefined
严格模式下函数执行的"."前没有对象this是undefined(非严格模式下是window)
非严格模式下没有写执行主体的情况下this都为window
非严格模式下函数执行前的"."的元素,如果没有则为window.

2.4 bind方法 #

这个方法在IE6/7/8不兼容;和call、apply相似,都是改变this的

var obj = {name:"Amy"};
function fn(){
  console.log(num1+num2,this);
}
fn.bind(obj,1,2);

bind只是改变函数中的this,并且给fn传递了参数,但是此时并没有执行fn,所以不输出任何内容

var temp = fn.bind(obj,1,2);
temp();

执行bind会有一个返回值

bind有预处理思想,事先把this改变为想要的结果,并把需要的值准备好,如果需要再直接执行即可
回顾this的四种情况:
1)自执行函数的this是window
2)给元素的某一行为绑定方法,this为当前元素
3)方法执行前的"."元素
4)构造函数中this就是当前实例

但是当前四种情况遇到call和apply的时候统统让路

3.求数组的最大值和最小值 #

求数组最大值和最小值的几种方法:

3.1 数组排序 #

现将数组排序,然后取第一个值和最后一个值

3.2 Math.min();Math.max(); #

  1. 这两个方法的参数不能接收数组
  2. 可以先把[12,1,41,5,2,4,6,3].join()/toString()数组转为字符串
  3. 然后使用eval()把一个字符串变为JS表达式执行
  4. 但是eval("12,1,41,5,2,4,6,3")只能获取到最后一项的值
    可以先将想要的内容拼接为字符串,然后再执行
    eval("Math.max("+arr.toString()+")");
    eval("Math.min("+arr.toString()+")");

一个括号中出现多项表达式,中间以逗号隔开,但是只能获取到最后一项,此为括号表达式
括号表达式:(x1,x2,x3...)括号表达式中出现多项内容时,并以逗号隔开时,只能获取或执行最后一项

function f1(){
  console.log(this);
}
function f2(){
  console.log(this);
}
var obj = {name:"Amy",fn:f2}
;(f1,f2)();  //window
;(f1,obj.f2)();  //window
;(obj.f2)();  //obj
;(f2)();  //window

3.3 假设法 #

假设数组第一个值为最大值,然后和后面的值逐个比较,遇到比假设值大的则替换,直到结束

var min = arr[0];
var max = arr[0];
for(var i=0;i<arr.length;i++){
  var cur = arr[i];
  cur>max ? max = cur : null;
  cur<min ? min = cur : null;
}

假设法类似自定义属性,这都是编程中的常用思想

3.4 apply参数 #

var arr = [12,15,46,53,32,16,33];
var a = Math.max.apply(null,arr);

4.求平均数 #

两种方法的比较:

var arr = [];
for(var i=0;i<arguments.length;i++){
    arr[arr.length] = arguments[i];
}
//模拟slice方法复制数组
Array.prototype.mySlice = function(){
  var arr = [];
  for(var i=0;i<this.length;i++){
      arr[arr.length] = this[i];
  }
  return arr;
};

由此可以得出将slice方法中的this替换为arguments即可,得出

Array.prototype.slice.call(arguments);

类数组使用数组的方法,需要转化

function avg(){
    Array.prototype.sort.call(arguments,function(a,b){return a-b;});
    [].pop.call(arguments);
    [].shift.call(arguments);
    return (eval([].join.call(arguments,"+"))/arguments.length).toFixed(2);
};
var res = avg(1,4,2,6,3,6,8,7,4,9);
console.log(res);

Number.toFixed(2) 保留小数两位

5.浏览器异常信息捕获 #

try{ 
    console.log(num)
}catch(e){
    console.log(e.message) //收集当前代码报错原因
}
console.log("num")

用try-catch捕获异常信息不影响后面的代码执行 如果try部分代码报错,会默认执行catch部分的代码

try{
    ...
}catch(){
    ...
}finally{
  //无论try代码是否报错,这部分代码一定执行
}

需求:既要捕获异常信息,又要让下面的代码不执行
那么手动抛出一条异常信息,以终止代码执行即可

try{
  console.log(num);
}catch(e){
  throw new Error("当前网繁忙,请稍后再试")
  //new ReferenceError  引用错误
  //new TypeError  类型错误
  //new RangeError  范围错误
}

兼容方法,将类数组转化为数组

var arr = [];
try{
  arr = Array.prototype.slice.call(likeArray);
}catch(e){
  for(var i=0;i<likeArray;i++){
    arr[arr.length] = likeArray[i];
  }
}

6.回调函数 #

把一个方法A当做参数值,传递给另外一个函数B,在B执行的过程中随时根据需求让A执行

function fn(callback){
  callback();
}
fn(function(){});

6.1 深入sort #

var arr = [1,9,2,8,3,8,4,7,5,6];
arr.sort(function(a,b){
  //每次执行匿名函数时,a为当前项,b为当前项的后一项
  //console.log(a,b);
  return a-b;
  return b-a;
  //return 的目的是返回一个大于0或小于0的数,大于0时a,b交换位置;小于0时a,b不变
});

6.2 二维数组的排序 #

var arr = [
  {name:"Amy",age:12},
  {name:"Bob",age:10},
  {name:"Cindy",age:14},
  {name:"Darin",age:11}
];
arr.sort(function(a,b){
  //按照年龄排序
  return parseFloat(a.age) - parseFloat(b.age);
  //按照字母排序
  return a.name.localeCompare(b.name);
});

console.log(arr);

6.3 汉字的排序 #

stringObject.localeCompare(target);
用本地特定的顺序来比较两个字符串:

如果 stringObject 小于 target,则localeCompare()返回小于0的数。
如果 stringObject 大于 target,则该方法返回大于0的数。
如果两个字符串相等,或根据本地排序规则没有区别,该方法返回 0。

返回值:
0 : 字符串匹配100%
1 : 不匹配,参数值来自于语言环境的排序顺序字符串对象的值之前
-1 : 不匹配,参数值来自于语言环境的排序顺序字符串对象的值之后

7.JSON及绑定数据 #

JSON格式:属性名被双引号包起来,且必须被双引号包起来才行

//JSON格式对象
var jsonObj = {"name":"Amy","age":12};

window对象中提供了JSON属性,并有两个方法:
JSON.parse(); 把 JSON格式的字符串 转化为 JSON格式的对象
JSON.stringify(); 把 JSON格式的对象 转化为 JSON格式的字符

在IE6、7中window下没有JSON对象,也就没有了parse和stringify方法
把JSON格式的字符串转换为JSON格式对象 的兼容方法:

var str = '{"name":"Amy","age":12}';
eval("("+ str +")")  //务必手动添加小括号

警告:关于JSON和eval需要注意的是:在代码中使用eval是很危险的,特别是用它执行第三方的JSON数据(其中可能包含恶意代码)时,尽可能使用JSON.parse()方法解析字符串本身。该方法可以捕捉JSON中的语法错误,并允许你传入一个函数,用来过滤或转换解析结果。如果此方法以备Firfox 3.5 、IE8 及 Safari 4 原生支持。大多数javascript类库包含的JSON解析代码会直接调用原生版本,如果没有原生支持的话,会调用一个略微不那么强大的非原生版本来处理。

7.1 DOM回流和重绘 #

回流(reflow):当页面中的html结构发生改变(增加、删除元素,位置移动)的时候,浏览器都需要重新进行计算DOM结构,重新对页面渲染
重绘(repaint):某一个元素部分样式改变了,浏览器只重新渲染这个元素

动态创建内容并添加到页面中去的方法:
方法1:动态创建元素节点并追加到页面的方式实现数据绑定
优势:把创建的内容追加到页面中,对原有内容没有影响
弊端:每创建一个元素添加到页面中的时候,就引发一次回流,影响性能
方法2:字符串拼接
优势:事先把内容拼接好,最后统一添加到页面中,只引发一次回流
弊端:将新拼接的字符串添加到原有结构中,原有绑定事件都取消了
字符串拼接 是工作中最常用的绑定数据方式

模板引擎数据绑定:jade,kTemplate,angularJS,backboneJS...原理都是字符串拼接

7.2 创建文档碎片 #

var frag = document.createDocumentFragment();

创建文档碎片相当于临时创建一个容器的对象
createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法

文档碎片的作用:每次JavaScript对DOM的操作都会改变页面的变现,并重新刷新整个页面,从而消耗了大量的时间。为解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片的内容一次性添加到document中。

使用完毕后

frag = null;

释放占用内存,使页面更干净

经IE和FireFox下测试,在append1000个元素时,效率能提高10%-30%,FireFox下提升较为明显。
不要小瞧这10%-30%,效率的提高是着眼于多个细节的,这将是一个质的飞跃,您也将步入骨灰级玩家的行列

7.3 DOM映射机制 #

页面中的标签和JS中获取到的元素对象或者元素集合是绑定在一起的;页面中的html结构改变了,JS中不需要重新获取,集合里面的内容也会自动改变

<ul class="list">
    <li>98</li>
    <li>99</li>
    <li>96</li>
    <li>97</li>
</ul>
var frag = document.createDocumentFragment();
for(var i=0;i<arr.length;i++){
    frag.appendChild(arr[i]);
}
oUl.appendChild(frag);
frag = null;
//现在应该是8条数据才对,但是只有4条

8.获取表格元素 #

获取table表格的各个元素方法
tableObject.rows 集合返回表格中所有行的一个数组
tableObject.cells 集合返回表格中所有单元格的一个数组
tableObject.tHead 集合返回表格中表头元素
tableObject.tBodies 集合返回表格中tbody元素集合

<table class="tab">
  <thead>
    <tr>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td></td>
    </tr>
  </tbody>
</table>
var tab = document.getElementById("tab");
var oHead = tab.tHead;  //获取thead元素
var oThs = oHead.rows[0].cells;  //获取第一行多个th元素
var oBody = tab.tBodies[0]; //获取tbody元素
var oTrs = oBody.rows;  //获取多个tr元素

9.节点元素的类 #

document.getElementsByTagName("div")
document.getElementsByClassName("div")
获取的是HTMLCollection元素集合类的实例
document.getElementsByName("list")
document.querySelectorAll("list")
获取的是NodeList节点集合类的实例
document.getElementById("lsit");
获取的是HTMLDivElement元素

var oList = document.getElementsByTagName("div");
var arr = Array.prototype.slice.call(oList);
在IE8及以下不支持借用数组slice将元素集合节点集合的类数组转化为数组的方法
但是arguments借用数组的方法不存在兼容问题