JS学习笔记03-对象操作
理解本节内容前最好先理解前面章节所记录的两条链。本文主要记录两方面内容:
- 对象的创建、继承
- 函数的闭包
一、对象
1.1、对象创建
1.1.1 构造模式
构造函数就是一个普通的函数,但是有自己的特征和用法:
- 构造函数名字的第一个字母通常大写。
- 函数体内部使用了
this关键字,代表了所要生成的对象实例。 - 生成对象的时候,必须使用
new命令。
该模式用于定义私有动态属性:每个私有属性都要在每个空对象temp上重新创建一遍,占用内存。
// 使用构造模式创建对象
function Person(name){
// 设置对象属性
this.name = name || "Bill";
this.age = 59,
this.sayName = function(){
console.info(this.name);
};
}
// 测试代码
var person = new Person('Bill');
// 翻译代码
var temp = {};
temp.__proto__ = Person.prototype;
Person.call(temp,"Bill");
var person = temp;
为了保证构造函数必须与new命令一起使用。有两种办法:
- 构造函数内部使用严格模式,即第一行加上
use strict。这样一旦忘了使用new命令,直接调用构造函数就会报错。由于严格模式中,函数内部的this不能指向全局对象,默认等于undefined,导致不加new调用会报错(JS不允许对undefined添加属性)。- 函数内部可以使用
new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。
new操作符是一个语法糖,JS主要做了如下事情:
- 创建一个空对象temp
- 让空对象temp的__proto__指向构造函数的prototype
- 使用apply/call执行构造器函数,第一个参数为该空对象temp
- 返回该空对象temp(当函数没有返回值时默认返回该函数的this)
1.1.2 原型模式
该模式用于定义共有静态属性:一个实例改变原型属性的值则其他实例都会受影响。
// 使用原型模式创建对象
function Person(name){}
// 原型设置
Person.prototype = {
constructor:Person,
name:"Bill",
age:59,
sayName:function(){
console.info(this.name);
}
}
// 测试代码
var person1 = new Person();
var person2 = new Person();
person1.__proto__.name = "Test";
console.info(person1.name); // Test
console.info(person2.name); // Test
1.1.3 圣杯模式
基本思想:每次创建的都是新创建的F构造函数实例,相互之间不会影响。其实就是Object.creat函数实现方式。
Object.create = function (obj) {
function F() {}
F.prototype = obj;
return new F();
};
生成实例对象的常用方法是,使用new命令让构造函数返回一个实例。但是很多时候只能拿到一个实例对象,它可能根本不是由构建函数生成的,所以能从一个实例对象生成另一个实例对象的办法是:通过Object.create来创建对象。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
1.2、对象继承
// 定义父类
function Animal(name){
this.name = name || "Animal";
this.run = function(){
console.info(this.name + '正在跑!');
}
}
Animal.prototype.eat = function(food){
console.info(this.name + '正在吃' + food);
};
1.2.1 构造继承
基本思想:在子类的构造函数的内部调用父类的构造函数。函数只是在特定环境中执行代码的对象,因此可通过使用call()和apply()方法在新创建的对象上执行构造函数。
明显缺点:只能继承父类的自身属性,不能继承父类的原型属性(实例并不是父类的实例,只是子类的实例 )。
// 定义子类
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// 测试代码
var cat = new Cat();
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
console.log(cat.name);
console.log(cat.run());
1.2.2 原型继承
基本思想:使用父类的实例对象设置为子类的原型对象。
明显缺点:父类的自身属性和原型属性是所有实例共享的,这是不能容忍的。
// 定义子类
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();// 这是重点
Cat.prototype.constructor = Cat;// 此时Cat.prototype.constructor为Animal,所以需要修复构造函数的指向
// 测试代码
var cat = new Cat();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
console.log(cat.name);
console.log(cat.run());
console.log(cat.eat('fish'));
1.2.3 圣杯继承
基本思想:每次继承的都是新创建的F构造函数实例,相互之间不会影响。
// 正常形式
Object.extend = function (Child, Parent) {
// 借用F这个中间量来继承,而不是直接共享原型
function F() {}
F.prototype = Parent.prototype;
// return new F();到这里就是Object.creat()的本质
Child.prototype = new F();
Child.prototype.constructor = Child;
}
// 使用形式
Object.extend(Cat, Animal);
// 翻译形式
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
1.3、对象克隆
对象克隆分为浅克隆和深克隆,针对引用值直接赋值的克隆操作为浅克隆,克隆的和被克隆的对象在克隆操作完成后,指向同一个地址引用,改变其中一个(注意:此处的改变为增加或删除对象的属性,而不是为该对象重新赋值一个对象),另一个也会改变,而深克隆则不会产生此现象。
// 对象的深度克隆(array/object/...)
function clone(obj){
var str1,str2 = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
if(typeof obj !== 'object') {
return;
}else if(window.JSON) {
str1 = JSON.stringify(obj);
str2 = JSON.parse(str1);
}else {
for(var prop in obj) {
str2[prop]=typeof obj[prop]==='object' ? clone(obj[prop]) : obj[prop];
}
}
return retObj;
}
二、函数
闭包的定义:可访问另一个函数作用域变量的函数。即闭包是个函数(这个函数能够访问其他函数的作用域中的变量)。匿名函数与闭包没有任何关系。实际上闭包是站在作用域的角度上来定义的:内部函数访问到外部函数作用域的变量,所以内部函数就是一个闭包函数。
2.1 作用
闭包的作用:
- 避免污染全局变量。
- 解决递归调用问题。
2.1.1 避免污染全局变量
ES6没出来之前,用var定义变量存在变量提升问题。当然现在大多用es6的let和const定义。eg:
for(var i=0; i<10; i++){
console.info(i)
}
alert(i);// 变量提升,弹出10
// 为了避免i的提升可以这样做
(function () {
for(var i=0; i<10; i++){
console.info(i)
}
})()
alert(i);// underfined。因为i随着闭包函数的退出,执行环境销毁,变量回收
2.1.2 解决递归调用问题
function factorial(num){
if(num<= 1) {
return 1;
} else {
// arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错
// 所以可以考虑借助闭包来实现
return num * arguments.callee(num-1);
}
}
var anotherFactorial = factorial;
anotherFactorial(4);
// 使用闭包实现递归
// 实际上起作用的是闭包函数f,而不是外面的函数newFactorial
function newFactorial = (function f(num){
if(num<1) {
return 1
} else {
return num* f(num-1)
}
})
2.2 坑点
闭包的坑点:闭包会携带外部函数的作用域,因此会比其他函数占用更多内容。过度使用闭包,会导致内存占用过多。
2.2.1 this指向问题
var object = {
name: "object",
getName: function() {
return function() {
console.info(this.name);
}
}
}
object.getName()();// underfined
2.2.2 内存泄露问题
function showId(){
var el = document.getElementById("app")
el.onclick = function(){
aler(el.id);// 这样会导致闭包引用外层的el,当执行完showId后el无法释放
}
}
// 改成下面
function showId(){
var el = document.getElementById("app");
var id = el.id;
el.onclick = function(){
aler(id);// 这样会导致闭包引用外层的el,当执行完showId后el无法释放
}
el = null;// 主动释放el
}

浙公网安备 33010602011771号