Fork me on GitHub
Javascript面向对象

 

Javascript基础回顾 之(三) 面向对象

 

本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者那里的一知半解,所以决定先花一些时间整理一下这些基础知识和大家分享。 后面会附上培训用的PPT。刚开始是打算写一篇的,但是后来写着写着就发现越来越多,所以决定还是写一个系列吧。本系列所有内容都是涉及Javascript基础的,没有时髦的玩意儿,但是我相信这些基础的东西会有助于你理解那些有趣的东西的。

  本篇是你必须知道的Javascript系列第三篇,我们主要来看看Javascript是如何面向对象的编程的。主要涉及以下内容 :

Javascript中的对象

什么是对象

  我们可以把Javascript中对象理解为一组无序的键值对,就好像C#中的Dictionary<string,Object>一样。Key是属性的名称,而value可以为以下3种类型:

  1. 基本值(string, number, boolean, null, undefined)
  2. 对象
  3. 函数
复制代码
var o = new Object();
o["name"] = "jesse";  //基本值作为对象属性
o["location"] = {     //对象作为对象属性
    "city": "Shanghai",
    "district":"minhang"
};

// 函数 作为对象属性 o[
"sayHello"] = function () { alert("Hello, I am "+ this.name + " from " + this.location.city); } o.sayHello();
复制代码

遍历属性

  在C#中我们是可以用foreach对Dictionary<string,Object>进行遍历的,如果说对象在Javascript中是一组键值对的话,那我们如何进行遍历呢?

复制代码
for (var p in o) {
    alert('name:'+ p + 
          ' type:' + typeof o[p]
        );
}
// name:name type:string
// name:location type:object
// name:sayHello type:function
复制代码

  上面的这种遍历方式会把原型中的属性也包括进来,关于什么是原型,以及如何区分原型和实例中的属性我们下面会讲到。

创建对象

  其实在上面我们已经创建了一个对象,并且使用了以下两种创建对象的方式。

  1. 利用new创建一个Object的实例。
  2. 字面量

  我们上面的o是用第一种方式创建的,而o中的location属性则是用字面量的方式创建的。而第一种方式其实也有一种名字叫做构造函数模式,因为Object实际上是一个构造函数,为我们产生了一个Object的实例。如果对于构造函数这一块还有不清楚的话,赶紧去看我的第一篇 类型基础Object与object吧

  除了以上两种方式以外,我们一些创建对象的方式,我们也来一起看一下:

工厂模式

复制代码
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('Jesse', 29, 'Software Engineer');
var person2 = createPerson('Carol', 27, 'Designer');
复制代码

  这种模式创建的对象有一个问题,那就是它在函数的内部为我创建了一个Object的实例,这个实例跟我们的构造函数createPerson是没有任何关系的。

  

  因为我在内部用new Object()来创建了这个对象,所以它是Object的实例。所以如果我们想知道它是具体哪个function的实例,那就不可能了。

构造函数模式

  工厂模式没有解决对象识别的问题,但是我们可以想一下,Object()实际上也是一个函数,只不过当我在它前面加上一个new的时候,它就变成了一个构造函数为我们产生一个Object的实例。那么我同样也可以在其它函数前面加上new这样就可以产生这个函数的实例了,这就是所谓的构造函数模式。

复制代码
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };
}

var p1 = new Person('Jesse', 18, 'coder');
alert(p1 instanceof Person); // true
复制代码

详解this

   this在Javascript中也可以算是一个很神奇对象,没错this是一个对象。我们在上一篇作用域和作用域链中讲到了变量对象,变量对象决定了在当前的执行环境中有哪些属性和函数是可以被访问到的,从某种程度上来说我们就可以把this看作是这个变量对象。我们之前提到了最大的执行环境是全局执行环境,而window就是全局执行环境中的变量对象,那么我们在全局环境中this===window是会返回true的。

  

  除了全局执行环境以外,我们还提到了另外一种执行环境,也就是函数。每一个函数都有一个this对象,但有时候他们所代表的值是不一样的,主要是这个函数的调用者来决定的。我们来看一下以下几种场景:

函数

function f1(){
  return this;
}

f1() === window; // global object

  因为当前的函数在全局函数中运行,所以函数中的this对象指向了全局变量对象,也就是window。这种方式在严格模式下会返回undefined。

对象方法

复制代码
var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // logs 37
复制代码

  在对象方法中,this对象指向了当前这个实例对象。注意: 不管这个函数在哪里什么时候或者怎么样定义,只要它是一个对象实例的方法,那么它的this都是指向这个对象实例的。

复制代码
var o = { prop: 37 };
var prop = 15;

function independent() {
    return this.prop;
}

o.f = independent;
console.log(independent()); // logs 15
console.log(o.f()); // logs 37
复制代码

 区别:上面的函数independent如果直接执行,this是指向全局执行环境,那么this.prop是指向我们的全局变量prop的。但是如果将independent设为对象o的一个属性,那么independent中的this就指向了这个实例,同理this.prop就变成了对象o的prop属性。

构造函数

   我们上面讲到了用构造函数创建对象,其实是利用了this的这种特性。在构造函数中,this对象是指向这个构造函数实例化出来的对象。

复制代码
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        alert(this.name);
    };
}

var p1 = new Person('Jesse', 18, 'coder');
var p2 = new Person('Carol',16,'designer');
复制代码

  当我们实例化Person得到p1的时候,this指向p1。而当我们实例化Person得到p2的时候,this是指向p2的。

利用call和apply

  当我们用call和apply去调用某一个函数的时候,这个函数中的this对象会被绑定到我们指定的对象上。而call和apply的主要区别就是apply要求传入一个数组作为参数列表。

复制代码
function add(c, d) {
    return this.a + this.b + c + d;
}

var o = { a: 1, b: 3 };

// 第一个参数会被绑定成函数add的this对象
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16

// 第二个参数是数组作为arguments传入方法add
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
复制代码

在bind方法中

  bind方法是 存在于function的原型中的 Function.prototype.bind,也就是说所有的function都会有这个方法。但我们调用某一个方法的bind的时候,会产生一个和原来那个方法一样的新方法,只不过this是指向我们传得bind的第一个参数。

复制代码
function f() {
    return this.a;
}

var g = f.bind({ a: "azerty" });
console.log(g()); // azerty

var o = { a: 37, f: f, g: g };
console.log(o.f(), o.g()); // 37, azerty
复制代码

在dom元素事件处理器中

  在事件处理函数中,我们的this是指向触发这个事件的dom元素的。

HTML代码

复制代码
<html>
<body>
    <div id="mydiv" style="width:400px; height:400px; border:1px solid red;"></div>
    <script type="text/javascript" src="essence.js"></script>
</body>
</html>
复制代码

JavaScript代码

function click(e) {
    alert(this.nodeName);
}

var myDiv = document.getElementById("mydiv");
myDiv.addEventListener('click', click, false);

  当我们点击页面那个div的时候,毫无疑问,它是会显示DIV的。

 详解prototype

  prototype即原型,也是Javascrip中一个比较重要的概念。在说原型之前呢,我们需要回顾一下之前的构造函数模式。在我们用构造函数去创建对象的时候主要是利用了this的特性。

复制代码
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        alert(this.name);
    };
}

var p1 = new Person('Jesse', 18, 'coder');
var p2 = new Person('Carol', 17, 'designer');
复制代码

  我们上面还讲到了当用Person实例化p1的时候Person中的this是指向p1的,当实例化p2的时候呢,this是指向p2的。那也就是说,p1和p2中的sayName虽然起到了同样的作用,但是实际上他们并非是一个函数。

  也就是说他们内存堆中是存在多份拷贝的,而不是在栈中引用地址的拷贝。先不说这符不符合面向对象的思想,至少这对于内存来说也是一种浪费。而解决办法就是我们要讨论的原型。

什么是原型

  在Javascript中的每一个函数,都会有一个原型对象,这个原型对象和我们普通的对象没有区别。只不过默认会有一个constructor属性指向这个函数。 同时,所有这个函数的实例都会有一个引用指向这个原型对象。如果不太清楚,那就看看下面这张图吧:

  以上就是构造函数,构造函数原型,以及实例之间的关系。以我们的Person构造函数为例,所有Person的实例(p1,p2)都舒服一个prototype属性指向了Person构造函数prototype对象。如此一来,我们就可以把方法写在原型上,那么我们所有的实例就会访问同一个方法了。

复制代码
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;

    Person.prototype.sayName = function () {
        alert(this.name);
    }
}
var p1 = new Person('Jesse', 18, 'coder');
var p2 = new Person('Carol', 17, 'designer');

alert(p1.sayName == p2.sayName); // true
复制代码

 什么是原型链

   大家还记得作用域链么?如果不记得,请自觉到第二篇中去复习(作用域和作用域链)。简单的来说,我们在一个执行环境中访问某个变量的时候如果当前这个执行环境中不存在这个变量,那么会到这个执行环境的包含环境也就是它的外层去找这个变量,外层还找不到那就再外一层,一直找到全局执行环境为止,这就是作用域链。而原型链有点类型,只不过场景换到了我们的对象实例中,如果我在一个实例中找某一个属性,这个实例中没有,那就会到它的原型中去找。记住,我们上面说了,原型也是一个对象,它也有自己的原型对象,所以就行成了一个链,实例自己的原型中找不到,那就到原型的原型对象中去找,一直向上延伸到Object的原型对象,默认我们创建的函数的原型对象它自己的原型对象是指向Object的原型对象的,所以这就是为什么我们可以在我们的自定义构造函数的实例上调用Object的方法(toString, valueOf)。

利用原型实现继承

  其实我们上面已经讲了继承在Javascript中的实现,主要就是依靠原型链来实现的。所有的实例是继承自object就是因为在默认情况下,我们所有创建函数的原型对象的原型都指向了object对象。同理,我们可以定义自己的继承关系。

复制代码
function Person(name, age, job) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function () {
    alert(this.name);
}

function Coder(language){
    this.language = language;
}
Coder.prototype = new Person(); //将 Coder 的原型指向一个Person实例实现继Person
Coder.prototype.code = function () {
    alert('I am a '+ this.language +' developer,  Hello World!');
}

function Designer() {
}
Designer.prototype = new Person(); //将 Desiger 的原型指向一个Person实例实现继Person
Designer.prototype.design = function () {
    alert('其实我只是一个抠图工而已。。。。');
}

var coder = new Coder('C#');
coder.name = 'Jesse';  
coder.sayName();  //Jesse
coder.code();     // I am a C# developer, Hello World!

var designer = new Designer();
designer.name = 'Carol';
designer.sayName();  // Carol
designer.design();   // 其实我只是一个抠图工而已。。。。
复制代码

原型链中的问题

  由于原型对象是以引用的方式保存的,所以我们在赋值的时候要特别注意,一不小心就有可能把之前赋的值给赋盖了。比如上面的代码中,我们先写原型方法,再实现继承,那我们的原型方法就没有了。

复制代码
function Coder(language){
    this.language = language;
}
Coder.prototype.code = function () {
    alert('I am a '+ this.language +' developer,  Hello World!');
}
Coder.prototype = new Person(); //这里会覆盖上面所有的原型属性和方法
var coder = new Coder('C#');
coder.name = 'Jesse';
coder.sayName();
coder.code();     // 这里会报错,找不到code方法。
复制代码
作者:Jesse 出处: http://jesse2013.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 
如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章! 
我会坚持以每周最少一篇的频率为大家带来我自己的或翻译一些国外的好的文章,想跟我一起进步么?那就【关注】我吧。
 
分类: JavascriptWeb

函数调用你知道几种方法

函数对于我们这些程序员来说,在熟悉不过啦,我们几乎每天能在写函数,使用函数。可是,在javascript中,大家知道几种函数调用的语句呢?在工作中,常用到的函数调用的语句就一两 个。那为啥大家知道我还在这里写博客,没事消磨大家的时间。想要知道,请耐心看完,你就明白啦。


javascript中的函数本身是一个变量/值,因此函数调用其实是一个表达式,如图1

所以,下面代码就是函数调用语句,它也是一个表达式语句:

functionName();

在javascript中具名函数可以使用上面方法直接调用,匿名函数可以通过引用变量调用,如果没有引用的匿名函数怎么调用呢?下面的例子说明这三种情况:

复制代码
// 实例1:具名函数直接调用
function fnName() {
    // 函数体
}
fnName();

// 实例2:匿名函数通过引用变量调用

var fnName = function () {
    // 函数体
};
fnName();

// 实例3:没有引用的匿名函数的调用(1)

(function () {
    // 函数体
}());

// 实例4:没有引用的匿名函数的调用(2)

(function () {
    // 函数体
})();

// 实例5:没有引用的匿名函数的调用(3)

void function () {
    // 函数体
}();
复制代码

实例1,2的用法比较常见,实例4在现在很多的框架中使用的也比较多,实例3,5见的就比较少,但是各有其用。

实例3,4都用于“调用函数并返回值”,这两种表达式有是那个括号,但是意义各不同。如图2,实例3的说明:

实例4的说明:

其实实例3,4基本是一致。但是它们的运算过程还是有不同:实例3是用强制运算符使函数调用运算得以执行,实例4则是用强制运算符运算“函数直接量声明”这个表达式,并返回一个函数 自身引用,然后通过函数调用运算符“()”,来操作这个函数的引用。

ps:“函数调用运算符()”在实例3中作用于匿名函数本身,而实例4中却是作用于一个运算的结果值。

最后的实例5,则用于“调用函数并忽略返回值”。运算符void用于使其后的函数表达式执行运算。然而由此带来的问题:如果不使用void和()这两个运算符,而直接使用下面的代码,是否能 使函数表达式执行呢?

复制代码
// 实例6:直接使用调用函数运算符"()"
function () {
    // 函数体
}()

// 实例7:使用语句结束符";"来执行语句
function () {
    // 函数体
}();
复制代码

实例6,7看起来是right,但是事实上它们都不可以执行,原因是它们无法通过脚本引擎的语法检测。在语法检测阶段,脚本引擎会认为下面的代码:

复制代码
function () {
    // 函数体
}

//

function fnName () {
    // 函数体
}
复制代码

结果是函数声明,因此实例6,7中使用具名函数也是通不过语法检测的,正因为这里是函数声明,所有实例6,7的代码位于函数后面的“()”没有语法意义,它们的代码被解析成了

复制代码
// 实例6:语法解释
function () {
    // 函数体
};
();

// 实例7:同上
复制代码

既然“function () {}”被当作完整的语法结构(函数声明语句)来解释,那么也就相当于已经存在语句结束符。因此“();”被当作一个语句表达式解释,而这样是错误的语法。所以,我们 能看到语法错误。

如此,这个语法错误是针对“();”,不是针对前面的函数声明的,下面代码稍作修改:

// 实例6:通过语法解释
function () {
    // 函数体
}(1,2)

这样就通过语法的解释, 因为语句被语法解释成了。

// 实例6:直接使用调用函数运算符"()"
function () {
    // 函数体
};
(1,2);

图4,被解释成了两个单值表达式,,也可以是单个单值表达式。但是这重要的是,这代码被解释成了一函数直接量声明和一个表达式语句,因此它不能起到“执行函数并传入参数”的作用。如果你真的想在声明的时候执行一下该函数,那么可以参考实例3,4,5,用“()”或void运算符将函数声明变成“单值表达式”

void function () {
    // 函数体
}(1,2);

当引擎在解释这样的代码时,由于先识别到运算符void,于是将后面的匿名函数识别为操作数。

上述就javascript中的函数调用语句,说实在的最后那个我也是看到书本上的,但是,我还是一直没太明白,如果那位高手可以指点其中的原委那就太感谢啦。也希望这个能帮到其他刚学习javascript的同学们。

 
 

AJAX POST&跨域 解决方案 - CORS

2014-01-06 09:09 by 聂微东, 627 阅读, 5 评论, 收藏编辑
  一晃又到新年了,于是开始着手好好整理下自己的文档,顺便把一些自认为有意义的放在博客上,记录成点的点滴。
     
 

 
  跨域是我在日常面试中经常会问到的问题,这词在前端界出现的频率不低,主要原因还是由于安全限制(同源策略, 即JavaScript或Cookie只能访问同域下的内容),因为我们在日常的项目开发时会不可避免的需要进行跨域操作,所以跨域能力也算是前端工程师的基本功之一。
  和大多数跨域的解决方案一样,JSONP也是我的选择,可是某天PM的需求变了,某功能需要改成支持POST,因为传输的数据量比较大,GET形式搞不定。所以折腾了下闻名已久的CORS(跨域资源共享,Cross-Origin Resource Sharing,这边文章也就是折腾期间的小记与总结。
     
     

 

概述

  • CORS能做什么:
     正常使用AJAX会需要正常考虑跨域问题,所以伟大的程序员们又折腾出了一系列跨域问题的解决方案,如JSONP、flash、ifame、xhr2等等。
     本文介绍的CORS就是一套AJAX跨域问题的解决方案。
 
  •  CORS的原理:
     CORS定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX 请求。实现此功能非常简单,只需由服务器发送一个响应标头即可。
 
  • CORS浏览器支持情况如下图:
       
     
  喜闻乐见、普大喜奔的支持情况,尤其是在移动终端上,除了opera Mini;
  PC上的现代浏览器都能友好的支持,除了IE9-,不过前端工程师对这种情况早应该习惯了...
     
 

CORS启航

 
  假设我们页面或者应用已在 http://www.test1.com 上了,而我们打算从 http://www.test2.com 请求提取数据。一般情况下,如果我们直接使用 AJAX 来请求将会失败,浏览器也会返回“源不匹配”的错误,"跨域"也就以此由来。
  利用 CORS,http://www.test2.com 只需添加一个标头,就可以允许来自 http://www.test1.com 的请求,下图是我在PHP中的 hander() 设置,“*”号表示允许任何域向我们的服务端提交请求
     
  也可以设置指定的域名,如域名 http://www.test2.com ,那么就允许来自这个域名的请求
     
     
  当前我设置的header为“*”,任意一个请求过来之后服务端我们都可以进行处理&响应,那么在调试工具中可以看到其头信息设置,其中见红框中有一项信息是“Access-Control-Allow-Origin:* ”,表示我们已经启用CORS,如下图。
  PS:由于demo都在我厂的两台测试机间完成,外网也不能访问,所以在这就不提供demo了,见谅
     
   简单的一个header设置,一个支持跨域&POST请求的server就完成了:)
 
  当然,如果没有开启CORS必定失败的啦,如下图:
  

问题&小结

  • 刚刚说到的兼容性。CORS是W3C中一项较新的方案,所以部分浏览器还没有对其进行支持或者完美支持,详情可移至 http://www.w3.org/TR/cors/
  • 安全问题。CORS提供了一种跨域请求方案,但没有为安全访问提供足够的保障机制,如果你需要信息的绝对安全,不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障,比如OAuth2。
 
  自认为的cors使用场景:
  • cors在移动终端支持的不错,可以考虑在移动端全面尝试;PC上有不兼容和没有完美支持,所以小心踩坑。当然浏览器兼容就是个伪命题,说不准某个浏览器的某个版本就完美兼容了,说不准就有点小坑,尼玛伤不起!~
  • jsonp是get形式,承载的信息量有限,所以信息量较大时CORS是不二选择;
  • 配合新的JSAPI(fileapi、xhr2等)一起使用,实现强大的新体验功能。
 
  如果觉得这文章也算用心,请劳驾点右下角的推荐。
  祝2014顺利。
 
  最后,有 北京&上海 的朋友想春节后想换工作的,【百度移动云事业部】期待聪明、勤奋的你 与我联系 (JD在页面右上角)
 
参考资料:
 
 
« 上一篇:javascript的那些事儿你都懂了吗
posted on 2014-01-06 22:55  HackerVirus  阅读(209)  评论(0编辑  收藏  举报