闭包
引入源 闭包
- Javascript特殊的变量作用域:全局变量和局部变量
- 函数内部可以直接读取全局变量;在函数外部自然无法读取函数内的局部变量
- 为了获取函数内部局部变量,在函数的内部,再定义一个函数
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立
既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,就可以在 f1 外部读取它的内部变量
- 闭包的概念:
上面的f2函数,就是闭包。
闭包就是能够读取其他函数内部变量的函数 - 除了读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中
其实是闭包函数的一个缺陷(太多闭包会占据太多内存)
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
由于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收
- 所以注意:
1——由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
2——闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
关于闭包里的this
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
这里getNameFunc()函数在调用的时候,其实是在windows环境下调用的,可以看成 var fun = object.getNameFunc()。this 应该为fun所在的环境即全局;
为了解决获取内部this,需要在对象内部属性里保存,如下:
var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
var that = this; // 将getNameFunc()的this保存在that变量中
var age = 15;
return function() {
return that.name;
};
}
}
alert(object.getNameFunc()()); // "My object"
原型链
this
引入源 this
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
- 简单来说,对于 obj.foo(),foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境
- 那函数的运行环境到底是怎么决定的?(这里不进行深入,请点击前面的连接)
快速排序
- 思想
- 在数据集之中,选择一个元素作为"基准"(pivot)
- 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边
- 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止
- Javascript语言实现
首先,定义一个quickSort函数,它的参数是一个数组
var quickSort = function(arr) {
};
然后,检查数组的元素个数,如果小于等于1,就返回。
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
};
接着,选择"基准"(pivot),并将其与原数组分离,再定义两个空数组,用来存放一左一右的两个子集
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2) ;
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
};
然后,开始遍历数组,小于"基准"的元素放入左边的子集,大于基准的元素放入右边的子集
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2) ;
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
};
最后,使用递归不断重复这个过程,就可以得到排序后的数组
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
模拟类(非语法糖的 class)
- 构造函数法
用构造函数模拟"类",在其内部用this关键字指代实例对象
function Cat() {
this.name = "大毛";
}
生成实例的时候,使用new关键字
var cat1 = new Cat();
alert(cat1.name); // 大毛
类的属性和方法,还可以定义在构造函数的prototype对象之上
Cat.prototype.makeSound = function(){
alert("喵喵喵");
}
- Object.create()法
"类"就是一个对象,不是函数
var Cat = {
name: "大毛",
makeSound: function(){ alert("喵喵喵"); }
};
然后,直接用Object.create()生成实例,不需要用到new
var cat1 = Object.create(Cat);
alert(cat1.name); // 大毛
cat1.makeSound(); // 喵喵喵
目前,各大浏览器的最新版本(包括IE9)都部署了这个方法。如果遇到老式浏览器,可以用下面的代码自行部署
if (!Object.create) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
!!注意:不能实现私有属性和私有方法,实例对象之间也不能共享数据,对"类"的模拟不够全面
- 极简主义法
这种方法不使用this和prototype,代码部署起来非常简单
封装
- 首先,它也是用一个对象模拟"类"。在这个类里面,定义一个构造函数createNew(),用来生成实例
var Cat = {
createNew: function(){
// some code here
}
};
- 然后,在createNew()里面,定义一个实例对象,把这个实例对象作为返回值
var Cat = {
createNew: function(){
var cat = {};
cat.name = "大毛";
cat.makeSound = function(){ alert("喵喵喵"); };
return cat;
}
};
- 使用的时候,调用createNew()方法,就可以得到实例对象
var cat1 = Cat.createNew();
cat1.makeSound(); // 喵喵喵
继承
调用后者的createNew()方法即可
- 先定义一个Animal类
var Animal = {
createNew: function(){
var animal = {};
animal.sleep = function(){ alert("睡懒觉"); };
return animal;
}
};
- 然后,在Cat的createNew()方法中,调用Animal的createNew()方法
var Cat = {
createNew: function(){
var cat = Animal.createNew();
cat.name = "大毛";
cat.makeSound = function(){ alert("喵喵喵"); };
return cat;
}
};
- 这样得到的Cat实例,就会同时继承Cat类和Animal类
var cat1 = Cat.createNew();
cat1.sleep(); // 睡懒觉
私有属性和私有方法
- 在createNew()方法中,只要不是定义在cat对象上的方法和属性,都是私有的
数据共享
- 有时候,我们需要所有实例对象,能够读写同一项内部数据。这个时候,只要把这个内部数据,封装在类对象的里面、createNew()方法的外面即可
var Cat = {
sound : "喵喵喵",
createNew: function(){
var cat = {};
cat.makeSound = function(){ alert(Cat.sound); };
cat.changeSound = function(x){ Cat.sound = x; };
return cat;
}
};
- 然后,生成两个实例对象:
var cat1 = Cat.createNew();
var cat2 = Cat.createNew();
cat1.makeSound(); // 喵喵喵
- 这时,如果有一个实例对象,修改了共享的数据,另一个实例对象也会受到影响
cat2.changeSound("啦啦啦");
cat1.makeSound(); // 啦啦啦
获取页面位置
从页面大小开始,两种方法:
-
clientHeight和clientWidth属性(不包括滚动条和border占用的空间)[注意下兼容性,有部分浏览器不知道]详情见
链接
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight -
包含滚动条在内的该元素的视觉面积(可以和上面一起用,取最大值 Math.max())
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight
绝对位置-获取元素相对于页面左上角的位置 :offsetTop和offsetLeft(表示该元素的左上角与父容器(offsetParent对象)左上角的距离),所以需要两个相加
function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
相对位置-指该元素左上角相对于浏览器窗口左上角的坐标
(将绝对坐标减去页面的滚动条滚动的距离就可以了)
滚动条滚动的垂直距离,是document对象的scrollTop属性;滚动条滚动的水平距离是document对象的scrollLeft属性
function getElementViewTop(element){
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null){
actualTop += current. offsetTop;
current = current.offsetParent;
}
if (document.compatMode == "BackCompat"){
var elementScrollTop=document.body.scrollTop;
} else {
var elementScrollTop=document.documentElement.scrollTop;
}
return actualTop-elementScrollTop;
}
获取元素位置的快速方法
- getBoundingClientRect()方法
相对位置:
var X= this.getBoundingClientRect().left;
var Y =this.getBoundingClientRect().top;
再加上滚动距离,就可以得到绝对位置:
var X= this.getBoundingClientRect().left+document.documentElement.scrollLeft;
var Y =this.getBoundingClientRect().top+document.documentElement.scrollTop;
不宜使用的语法
源 源
- 永远只使用===和!==
- switch 贯穿,凡是有case的地方,一律加上break
- 单行的块结构
if、while、do和for,都是块结构语句,但是也可以接受单行命令
建议不管是否只有一行命令,都一律加上大括号 - ++和- -
表面上可以让代码变得很紧凑,但是实际上会让代码看上去更复杂和更晦涩。因此为了代码的整洁性和易读性,不用为好【这个,,,,】 - function语句
function foo() { }
和
var foo = function () { }
但是在解析的时候,前一种写法会被解析器自动提升到代码的头部,因此违背了函数应该先定义后使用的要求,所以建议定义函数时,全部采用后一种写法
6. 基本数据类型的包装对象
new String("Hello World");
new Number(2000);
new Boolean(false);
这样写完全没有必要,而且非常费解,因此建议不要使用
——new Object和new Array也不建议使用,可以用{}和[]代替
7. new语句(类)
一种变通方法
Object.beget = function (o) {
var F = function (o) {};
F.prototype = o ;
return new F;
};
创建对象时就利用这个函数,对原型对象进行操作
var Cat = {
name:'',
saying:'meow'
};
var myCat = Object.beget(Cat);
对象生成后,可以自行对相关属性进行赋值
myCat.name = 'mimi';