JavaScript模式

概要

这篇文章主要对《javascript模式》这本书上的一些点做了一些笔记和解释。内容主要来源于郑安源读书分享会的ppt。

内容大致分为这几个方面:

  • 介绍
  • 基本技巧
  • 字面量与构造函数
  • 函数
  • 对象构造模式

浅谈几个属性

在介绍这些概念之前,首先要对原型链,闭包,作用域,上下文,this,这几个js里经常谈论的东西有一定的理解。在下文遇到这几个东西我会简单的解释一下

这里有一篇博客,对这几个性质阐述的非常清楚,如果想理解的稍微透彻一点,吃透这篇博客。。。。

深入理解javascript原型和闭包:http://www.cnblogs.com/wangfupeng1988/p/3977924.html

1.介绍

js的几个概念

  • Object-based

  • NO Classes

  • Prototypes

关于第一点,js是一种对象使用的很普遍的语言。

关于第二点,在之前js是没有class的,也就是类,但是在es6中,js又添入了class的概念(但是现在基本用不到,想了解class的话,请了解java中class的概念,这里不细谈)

关于第三点,也是很重要的一点,也就是原型,每一个函数都有一个prototype属性,这个属性是一个对象,其constuctor属性指向原函数,实现代码复用、继承,在这里作简要的解释

 

首先要理解什么是构造函数,什么是实例对象

什么是构造函数?和new一起使用的函数就是构造函数。构造函数的目的就是为了方便创建一个实例。方便代码的复用

类似于构造函数就是一个泛指的人,当给了这个人具体特征(名字,特点)的时候,他就变成了一个具体的人,每个具体的人彼此间有差异,这也就是一个个实例

 

 

看下面这段代码

 

function Person(name){
         this.name=name;        //当和new一起使用时,this指向新实例对象,解释在下一张图
         this.sayName=function(){
             alert(this.name);
         };
     }

//person作为构造函数

var person1 = new person('Bob');//构造了第一个person实例对象
      alert(person1.sayName);  //Bob

var person2 = new person('Taniffer');//构造了第二个person实例对象
      alert(person2.sayName);  //Taniffer

一般情况下,this指向他上一级对象,person1.sayName;  sayName里的this指向的是person1(”.”前面的对象)。当时用构造器的时候,会对构造器下图所示的改动,使得this的指向变为一个空对象。

image

 

下面是第三点的解释

每一个对象(包括实例对象)都会有一个proto属性,这条属性是链式的,不断指向它构造函数的prototype属性,一直指向链的末尾,也就是null,这也就是原型链,

当在实例对象中中查找属性时,如果没有,则顺着原型链一直向上查找

看下面这段代码

function foo(){}
foo.prototype.z = 3 //函数foo有一个prototype属性(这个属性值是对象),在这个属性里z=3

var obj =new foo();//在这里构造了一个实例对象,而这个实例对象的原型链先指向构造它的函数的prototype,也就是foo.prototype
obj.y = 2;
obj.x = 1;

obj.x; // 1
obj.y; // 2
obj.z; // 3     这里得到3,是因为在实例对象中没有找到3,顺着原型链向上查找,则得到了这3
typeof obj.toString; // ‘function'
'z' in obj; // true
obj.hasOwnProperty('z'); // false,这个函数是用来检查对象本身是否存在这个属性'z',没有则返回false
image

2.基本技巧

  • 减少全局变量

  • 只使用一个var声明

  • 声明的提升

  • for循环和for-in循环中的一些注意事项

  • 避免使用eval

1.减少全局变量的使用

全局变量的危害:在项目中经常会引用到第三方库,有可能产生命名冲突,导致代码不能工作

image

 

2.只使用一个var声明

image

 

3.变量声明的提升

在js执行过程中,分为初始化阶段和代码执行阶段,在初始化阶段所有的变量声明会得到提升。

如下图,左边和右边的函数是完全等价的。这在后面函数声明的那一块还会再提到。

image

 

4.for循环中的一些注意事项

第一点。关于length

在下图中,左图和右图的代码,哪个更好一点呢?

image

现在可能看不出来,但是看这句话

具有length属性不一定是数组,还可能是HTMLCollection

什么是HTMLcollection?

在使用dom操作时,有时可能会用到这些操作

var myarray = document.getElementsByClassName()

var myarray = document.getElementsByTagName()

var myarray = document.getElementsByName()

而因为get到的元素有多个,这些dom操作会生成一个类似数组的东西,这个东西就是HTMLcollection,

但是由于HTMLcollection是动态的,所以每次调用myarray.length的时候,都会重新调用 document.getElementsByxxxx() 计算myarray的值,再计算myarray.length,这样就增加了很多不必要的开销。

 

第二点,关于for-in

for-in循环是一种很好用的遍历方法,可以遍历对象中的元素,但是它有一个缺点就是他会顺着原型链往上遍历,将原型链上的属性也遍历出来。回到上面那张图

image
 
for-in会遍历出x,y,z三个值,z可能是我们不想要的。这是我们只需要在遍历的时候加上一些小手段
 image
这样就不会遍历出对象原型链上的值了。
 

5.避免使用eval

eval是什么?
eval可以使传入的字符串直接当做代码段执行,这样就可能产生全局变量的污染,也可能因为恶意代码产生不安全的因素
image

3 字面量与构造函数

首先,什么是字面量,字面量就是在声明对象或者数组之类的时候,尽量采用直接把内容写出来的方法,也就是下图的第一种声明方法。

image

对于第三点,这里做一下解释如下图。

image

 

4.函数

1.函数的几种形式

image

2.关于声明的提升

这里会连着上文的提升一起做个总结

先看这段代码

func1();    // 输出:我是函数声明

func2();    // 报错 

console.log(a); // 输出:undefined

function func1() {
    console.log("我是函数声明");
}

var func2 = function() {
    console.log("我是函数表达式");
}

var a = 10;

因为JS会对函数声明前置,所以func1在函数声明前面执行依旧得到正确答案,而函数表达式func2则报错,为什么?我们先要弄清楚:

 

函数的执行分为两个阶段——变量初始化代码执行。在变量初始化阶段,声明按照如下顺序提升:
1. 函数参数(若未传入,初始化该参数值为undefined)
2. 函数声明(若发生命名冲突,会覆盖)
3. 变量声明(初始化变量值为undefined,若发生命名冲突,则忽略)

看下面一段代码

a=10;

function test(a, b) {
    var c = 10;
    function d() {}
    var e = function _e() {}
    (function x() {});
    b = 20;
}
test(10);

// 变量初始化阶段各个属性的值
AO(test) = {
    a:10,
    b:undefined,
    c:undefined,
    d:<ref to func "d">,
    e:undefined
};

// 代码执行阶段各个属性的值
AO(test) = {
    a:10,
    b:20,
    c:10,
    d:<ref to func "d">,
    e:function _e() {}
};

 

3.回调函数

将函数作为参数传入另一个函数,这个参数就叫回调函数

回调函数的一个错误用例

image

为什么是undefined呢,在这里,foo(obj.getVal);就相当于将foo(function(){return this.val})传入;

相当于

function foo (){
    return  this.val;    
}

foo();

这里的this在foo里,相当于global.foo();

所以this指向全局,全局里没有val,输出undefined;

 

要解决这种错误

可以同时将对象传入

image

关于call这个函数.

a.call(b);简单来说,就是将a这个方法放在b中来用

在上文就是将getVal这个方法放在obj中来用,也就是obj.getVal;所以输出得到正确的值

 

4.给自己赋值的函数

image

 

5.立即函数表达式

image

 

6.带记忆的函数

image

 

7.使用对象传递参数

image

 

8.生成部分应用函数

通过只给函数提供部分参数,从而生成一个函数,这个生成的函数所接收的参数数量等于之前少传入的参数数量

image

柯里化函数不会一次性取完所有参数,而是在每次调用时只取一个参数,并返回一个函数来取下一个参数

这样的好处是我们只要以部分的参数来调用某函数,就可以得到一个部分应用函数,部分应用函数所接收

的参数数量等于之前少传入的参数数量。部分应用使得构造新函数快捷方便

 

5.对象构造模式

1.命名空间

先来看一段相对来说不好的js写法

image

那么该怎么处理呢,这时候就引入了命名空间

image

当函数属性发生嵌套,生成的便是嵌套的命名空间

image

自动生成命名空间的函数

var MYAPP = MYAPP || {};
MYAPP.namespace = function(ns_string) {
    var parts = ns_string.split('.'),
        parent = MYAPP,
        i;

    // 剥离最前面的冗余全局变量
    if (parts[0] === "MYAPP") {
        parts = parts.slice(1);
    }

    for (i=0; i<parts.length; i++) {
        // 如果不存在,就创建一个属性
        parent[parts[i]] = parent[parts[i]] || {};
        parent = parent[parts[i]];
    }
    return parent;
}

// 测试
var module1 = MYAPP.namespace('MYAPP.modules.module1');
module1 === MYAPP.modules.module1;

// 忽略最前面的MYAPP
var module2 = MYAPP.namespace('modules.module2');
module2 === MYAPP.modules.module2;

// 长命名空间
MYAPP.namespace('one.two.three.four.five.six.seven.eight');

2.声明依赖

image

 

 

3.沙盒模式

命名空间也是会产生一些弊端的:

1. 不可避免要产生一个全局变量

2. 如果命名空间嵌套得很深,写起来麻烦

由此便引入了沙盒

将我们的代码写在一个”沙盒“里,这个沙盒产生一个作用域,我们的代码在其中运行,不会产生全局变量

沙盒是封闭的?如果想要使用第三方库怎么办?沙盒还应给我们提供所需的依赖

沙盒的基本框架:

image

实现沙盒构造函数

image

image

image

posted @ 2017-05-04 21:00  Taniffer  阅读(240)  评论(0编辑  收藏  举报