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
}
posted @ 2015-04-16 15:56  VPDong  阅读(125)  评论(0)    收藏  举报