重读《JavaScript高级程序设计》

life/learn/read/javascript/javascript_high_level

最近自己在休假,打算闭门几天将《JavaScript高级程序设计》(第3版)这本良心教材再回顾一遍。目前自己进入前端领域两年多,现在重读并记录下这本教材的“硬”知识点 😊 。

函数没有重载

ECMAScript 函数不能像传统意义上那样实现重载。而在其他语言(如Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数类型和数量)不同即可[p66]。ECMAScript的类型是松散形的,没有签名,所以是没有重载的。

function load(num){
    return num + 100;
}
function load(num,name){
    return num + 200;
}
var result = load(100); // 300
# 后面的函数声明覆盖掉前面的函数声明

基本的数据类型

基本类型值指的是简单的数据段,而引用类型指那些可能由多个值构成的对象[p68]。这里指出来的基本的数据类型是说的es5的哈:Undefined,Null,Boolean,NumberString

传递参数

ECMAScript 中所有的函数的参数都是按值传递的[p70]。也就是说,把函数外部的值复制给函数内部的参数,就是把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。下面分开例子介绍两种不同类型为什么是按值传递。

基本类型值

基本类型这个按值传递比较好理解,直接复制变量的值传递:

function addTen(num){
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
console.log(result); // 30
console.log(count); // 20 ,没有变化哈

引用类型值

有些人认为引用类型的传参是按照引用来传的,那暂且认为他们的理解是正确的,那下面的示例结果怎么解析呢?

function setName(obj){
    obj.name = '嘉明';
    obj = new Object();
    obj.name = '庞嘉明';
}
var person = new Object();
setName(person);
console.log(person.name); // '嘉明',为啥不是'庞嘉明'呢?

如果是按照引用传的话,那么新建的对象obj = new Object()应该是指向堆内容的对象啊,那么改变它本有的name属性值应该生效,然而并没有生效。所以它也是按值传递滴。

函数声明与函数表达式

解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁[p111]。解析器会率先读取函数声明,并使其执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析。

console.log(sum(10 , 10)); // 20
function sum(num1 , num2){
    return num1 + num2;
}
console.log(sum(10 , 10)); //TypeError: sum is not a function
var sum = function(num1 , num2){
    return num1 + num2;
}

apply和call

每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值[116]。call和apply在对象中还是挺有用处的。

apply()方法和call()方法的作用是相同的,区别在于接收参数的方式不同。

apply

apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组,这里的参数数组可以是Array的实例,也可以是arguments对象(类数组对象)。

function sum(num1 , num2){
    return num1 + num2;
}
function callSum1(num1,num2){
    return sum.apply(this,arguments); // 传入arguments类数组对象
}
function callSum2(num1,num2){
    return sum.apply(this,[num1 , num2]); // 传入数组
}
console.log(callSum1(10 , 10)); // 20
console.log(callSum2(10 , 10)); // 20

call

call()方法接收的第一个参数和apply()方法接收的一样,变化的是其余的参数直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来。

function sum(num1 , num2){
    return num1 + num2;
}
function callSum(num1 , num2){
    return sum.call(this , sum1 , sum2);
}
console.log(callSum(10 , 10)); // 20

创建对象

虽然Object构造函数或者对象字面量都可以用来创建单个对象,但是这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。[p144]

工厂模式

工厂模式就是造一个模子产生一个个对象。

 function createPerson(name , age ,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };
    return o;
 }
 
 var person1 = createPerson('nicholas' , 29 , 'software engineer');
 var person2 = createPerson('greg' , 27 , 'doctor');

工厂模式解决了创建多个相似对象的问题(解决创建对象时产生大量重复代码),但是没有解决对象识别的问题(即怎么知道一个对象的类型,是Person还是Animal啊)。

构造函数模式

下面使用构造函数创建特定类型的对象。这里是Person类型:

function Person(name , age , job){ // 注意构造函数的首字母为大写哦
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}

var person1 = new Person('nicholas' , 29 , 'software engineer');
var person2 = new Person('greg' , 27 , 'doctor');

alert(person1.constructor == Person); // true 可以理解为person1的创造者是Person,也就是对象的类型Person

在创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

构造函数解决了重复实例话问题(也就是创建多个相似对象的问题)和解决了对象识别的问题。但是,像上面那样,person1和person2共有的方法,实例化的时候都创建了,这未免多余了。当然可以将共有的方法提取到外面,像这样:

function Person(name , age , job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
}
var person1 = new Person('nicholas' , 29 , 'software engineer');
var person2 = new Person('greg' , 27 , 'doctor');

将sayName提取出来,就成了全局的方法了,然而这里只有Person类创建对象的时候才使用到,这样就大才小用了吧,所以提取出来到全局方法这种操作不推荐。

原型模式

创建的每个函数都有一个prototype(原型)属性,这个属性就是一个指针,指向一个对象,而这个对象的用途就是包含可以由特定类型的所有实例共享的属性和方法。

function Person(){
}
Person.prototype.name = 'nicholas';
Person.prototype.age = 29;
Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
person1.sayName(); // nicholas

var person2 = new Person();
person2.sayName(); // nicholas

console.log(person1.sayName == person2.sayName); // true

可以有关系图如下:

life/learn/read/javascript/prototype_object

上面的Person.prototype不建议使用字面量来写Person.prototype={},虽让效果一样,但是这里重写了原本Person.prototype的对象,因此constructor属性会指向Ohject而不是Person。当然也是可以处理的啦,将指向指正确并指定'construtor'的枚举属性为enumerable: false

原型模式解决了函数共享的问题,但是也带了一个问题:实例化中对象的属性是独立的,而原型模式这里共享了。

组合使用构造函数模式和原型模式

创建自定义类型的最常见的方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。

function Person(name , age ,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['shelby' , 'court'];
}
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person('nicholas' , 29 , 'software engineer');
var person2 = new Person('greg' , 27 , 'doctor');

person1.friends.push('van');
console.log(person1.friends); // 'shelby,court,van'
console.log(person2.friends); // 'shelby,court'
console.log(person1.friends === person2.friends); // false
console.log(person1.sayName === person2.sayName); // true

动态原型模式

其他的OO语言,比如java,创建对象的类中是包含了自身的属性、方法和共有的属性、方法,如下小狗的例子:

public class Dog{
    int age;
    public Dog(String name ){
        this.age = age;
        System.out.println('小狗的名字是: ' + name);
    }
    public void setAge(int age){
        age = age;
    }
    public int getAge(){
        System.out.println('小狗的年龄为: ' + age);
        return age;
    }
    
    public static void main(String []args){
        /* 创建对象 */
        Dog dog = new Dog('tom');
        /* 通过方法来设定age */
        dog.setAge(2);
        /* 调用另外一个方法获取age */
        dog.getAge();
        /* 也可以通过 对象.属性名 获取 */
        System.out.println('变量值: ' + dog.age);
    }
}

为了看起来是类那么一会事,动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。如下:

function Person(name , age ,job){
    // 属性
    this.name = name;
    this.age = age;
    this.job = job;
    // 方法
    if(typeof this.sayName != 'function'){
        Person.prototype.sayName = function(){
            alert(this.name);
        }
    }
}
var friend = new Person('nicholas' , 29 , 'software engineer');
friend.sayName();

寄生构造函数模式

在前面几种模式都不适应的情况下,可以用寄生构造函数模式(数据结构中就使用到哈),寄生构造函数模式可以看成是工厂模式和构造函数模式的结合体。其基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。

function Person(name , age , job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    }
    return o;
}

var friend = new Person('nicholas', 29 , 'software engineer');
friend.sayName(); // nicholas

关于寄生构造函数模式,需要说明:返回的对象与构造函数或者与构造函数的原型属性直接没有什么关系;也就是说,构造函数返回的对象与构造函数外部创建的对象没有什么区别。为此,不能依赖instanceof操作符来确定对象类型。由于存在上面的问题,建议在可以使用其他模式的情况下,不要使用这种模式。

稳妥构造函数模式

还有 71% 的精彩内容
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容