标签 : ES5
1.闭包
观察下一段代码在浏览器中的运行情况
(function(){
var a = 30;
})();
alert(a); // Uncaught ReferenceError: a is not defined
浏览器会报错,因为上面代码段1-3行形成了闭包,从而导致闭包外面无法访问到闭包内的变量。
我们再观察这么一段代码:
(function(){
alert(a); // undefined
var a = 30;
})();
上面这段代码结果是a是undefined,和上面不同,undefined是指a声明了,但是没有赋值,而上面是报错是因为作用域中没有a这个变量。
接着看下面这段代码
function f1() {
var a = 20;
function f2() {
a += 1;
console.log(a);
}
return f2;
}
var r = f1();
r(); // 21
r(); // 22
r(); // 23
我们在函数作用域外面访问到了f1中的变量,这就是闭包的作用,让我们可以拿到本不该拿到的东西。
内存泄漏
我们可能经常听说闭包会导致内存泄漏,那么为什么闭包会导致内存泄漏呢?
一般函数在执行完的时候,js的垃圾回收机制就会回收函数内的变量,释放内存。但是,上面我们把f2函数给 return 出来了,里面使用到了a变量,而浏览器不知道我们执行了上面三次之后什么时候再使用个函数,所以a变量就一直在内存中存在了,从而可能会导致内存泄漏。那么,我们怎么防止发生内存泄露呢?一般情况下,我们在不使用f2的时候,把f2置为 null。
f2 = null;
有些时候,我们想保护我们的变量,这时候可以用到闭包,不让外部直接访问和修改我们的变量。我们可以通过下面的方式实现:
function Man() {
var name;
this.setName = function (value) {
name = value;
}
}
var s = new Man();
s.setName('lemon');
现在,name 变量就被我们保护了,切记不用的时候一定要释放内存。
现在我们看一个我们经常看到的一个例子:
var list_li = document.getElementsByTagName('li'); // 假设有6个li
for(var i = 0;i<list_li.length;i++) {
list_li[i].onclick = function () {
console.log(i);
}
}
我们的目的是去给每一个li绑定一个事件,点击第 i 个我们就输出对应 i 的值,但是我们发现结果都是6。怎么去修改处我们想要的结果,也许都知道。
var list_li = document.getElementsByTagName('li');
for(var i = 0;i<list_li.length;i++) {
(function (i) {
list_li[i].onclick = function () {
console.log(i);
}
})(i);
}
为什么上面的代码执行结果和这次的不一样呢?
在js分为 同步队列 和 异步队列,只有同步队列里面的代码执行完了才会去异步队列里面拉取执行。
异步队列只包含以下三个:
- 事件绑定
- Ajax
- setTimeout
接下来,我们就清楚了,事件绑定是异步队列,而for循环是同步队列,当for结束的时候,i的值自然就是最后一个值。但是,我们加上闭包以后,将i传进去,但是函数没有执行完,因为事件绑定在异步队列中,此时i就会保存在内存中,一直到对应的事件绑定事件结束,i才会被垃圾回收机制给回收掉。
上面这一整句话都是因为js在es5中只有函数级作用域,没有块级作用域,而es6中引入的let则会产生块级作用域,这里不做赘述。
2. 模块化
立即执行函数实现模块化
var module = (function () {
var a =5;
function add(x) {
var b = a + x;
console.log(b);
}
return {
des:'这是一个模块',
add:add
}
})();
module.add(3)
加入你感觉这种写法过于函数时,你也可以写成这样:
var module = {
a:5,
add:function (x) {
console.log(this.a + x)
}
};
module.add(30)
3. this
var a = 20;
var p = {
a:30,
test:function () {
alert(this.a)
}
}
p.test(); // 30
var q = p.test();
q(); // 20
p.test() 中 this 指向 p ,所以 a 的值为30。上面的 var q = p.test() 相当于下面的代码:
var q = function () {
alert(this.a)
}
此时 this 指向的是全局,所以 a 的值为20。
观察下面的代码
var a = 20;
var p = {
a:30,
test:function () {
function s() {
alert(this.a)
}
s();
}
};
p.test() //20
在函数里面执行 s ,我们去找 s 的宿主,假如我们没有找到s的宿主,默认就会去找到window。
4. ES5中的类
在别的语言中,类的构造函数需要我们显式的声明:
class c{
constructor{
this.a = 20;
}
}
但是在js中,构造函数和初始化这个类就是一个东西
var People = function (sex) {
this.sex = sex; // 构造函数和初始化这个类就是一个东西
};
People.prototype.hobby = function () {
console.log(this.sex + '喜欢女人!!')
};
父类声明好了,继续写我们的子类。
第一种写法:
var Lemon = function () {
};
Lemon.prototype = People.prototype;
乍一看没啥问题啊,子类的原型等于父类的原型。但是,我们继续做一件事情:
Lemon.prototype.test = function () {
}
我们给子类的原型上挂载一个方法,此时我们去consol.log 父类,发现父类的原型上也多了一个test方法。子类只是继承我们的父类,怎么可以修改呢!!!至于为什么呢?因为原型的传递是按引用传递。
所以,第一种方法是不可行的。
第二种写法:
子类不是要把父类的方法和属性都继承过来么,那就这样写:
var Lemon = function (sex) {
People.call(this,sex); // 把People的属性继承过来
};
Lemon.prototype = new People(); // 把People的方法继承过来
var l = new Lemon('Man');
console.log(l)
这样写看起来还不错,方法和属性都继承过来了。但是,我们修改我们的父类如下:
var People = function (sex) {
this.sex = sex;
console.log('666')
};
现在我们new Lemon的时候,会发现输出了 两次 666,也就是构造函数执行了两次,而一旦我们构造函数里面的东西比较多,这时候就变得比较坑了。这么看来,这第二种方法也不怎么好,故舍弃。
通过上面两种方法,我们仔细观察,子类的构造函数的执行都是指向了父类。所以,我们需要了解一下我们现在的需求:
- 拿到父类原型链上的方法
- 不能让构造函数执行两次
- 引用的原型链不能是按址引用
- 修正子类的constructor
基于以上的需求:
var Lemon = function (sex) {
People.call(this,sex); // 把People的属性继承过来
};
var __bak = Object.create(People.prototype); // 创建一个副本
__bak.constructor = Lemon; // 修正构造函数的指向
Lemon.prototype = __bak; // 把People的方法继承过来
var l = new Lemon('Man');
console.log(l)
上面就是所谓的我们基于 js 实现面向对象的一个过程。
5. bind
var user = {
age:20,
init:function () {
console.log(this.age)
}
};
var data = {
age: 40
};
var s = user.init.bind(data);
s();
这里我们要注意,bind之后返回的是一个新的对象。
总结
1.立即执行函数
2.闭包
- 内部函数可以访问外部函数的变量,把内部函数返回出去就可以保护内部的变量。
- 闭包容易造成内存泄漏,避免的方法就是 不用的时候将 return出去的变量置为 null。
3.原型链
- 构造函数的属性比原型链上的属性的优先级要高;
- 面向对象编程的时候,js没有类的概念,可以用函数来替代;
- constructor 实际就是对应那个函数;
- prototype 是按引用传递的,可以用 Object.create 创建原型链的副本
4. 数据传递类型
- 数值 字符串 布尔类型 都是按值传递;
- 对象数组都是按引用传递;
5.改变this的指向,其中bind返回一个新对象
- call
- apply
- bind
网友评论