javascript 高级特性探讨A4-A5(call和原型.对象复制)

  在js中,call和apply是二个神奇的方法,但同时也是容易令人迷惑的二个方法,call和apply的功能是以不同的对象作为上下文来调用某个函数的,简而言之,就是允许一个对象去调用另一个对象的成员函数,咋一看似乎很不可思议,而且还容易引起混乱,但其实js并没有严格的所谓’成员函数‘的概念,函数与对象的所属关系在调用时才展现出来,灵活使用call和apply可以节省不少时间,在后面我们可以看到,call可以用于实现对象的继承

  call和apply的功能是一致的,二者细微的差别在于call以参数表来接受被调用函数的参数,而apply以数组来接受被调用函数的参数,call和apply的语法分别是:

    

func.call(thisArg[, arg1[, arg2[, ... ]]])
func.apply(thisArg[, argsArray] )

  其中,func是函数的引用,thisArg是func被调用时的山修改文对象,arg1,arg2或argsArray是传入func的参数,我们以下面一段代码为例介绍call的工作机制:

 1 var someuser = {
 2     name: 'by',
 3     display: function (words) {
 4         console.log(this.name + ' says ' + words);
 5     }
 6 };
 7 var foo = {
 8     name: 'foo'
 9 };
10 someuser.display.call(foo, 'hello'); //输出foo says hello

someuser.display是被调用的函数,它通过call将上下文改变为foo对象,因此在函数体内访问的this.name时实际上访问的就是foo.name,因此输出的即是foo.

2bind

如何改变被调用函数的上下文呢?前面说过,可以用call或apply方法,但如果重复使用不方便,因此每次都要把上下文对象作为参数传递,而且还会使代码变得不直观,针对这种情况,我们可以使用bind方法来永久的绑定函数的上下文,使其无论被谁调用,上下文都是固定的,bind语法如下:

func.bind(thisArg[, arg1[, arg2[, ...]]])

 其中func是待绑定函数,thisArg是改变的上下文对象,arg1,arg2是绑定的参数表,bind方法返回值是上下文为thisArg的func,通过下面这个例子可以帮我们理解bind的使用方法:

 1 var someuser = {
 2     name: 'by',
 3     func: function () {
 4         console.log(this.name);
 5     }
 6 };
 7 var foo = {
 8     name: 'foo'
 9 };
10 foo.func = someuser.func;
11 foo.func(); //输出foo
12 
13 foo.func1 = someuser.func.bind(someuser);
14 foo.func1(); //输出by
15 
16 func = someuser.func.bind(foo);
17 func(); //输出foo
18 
19 func2 = func;
20 func2(); //输出foo

 上面代码直接将foo.func 赋值给someuser.func,调用foo.func()时, this指针为foo,所以输出结果是foo.

foo.func1使用了bind方法,将someuser作为this指针绑定到someuser.func,调用foo.func1()时,this指针为someuser,所以输出结果是by.

全局函数func同样使用了bind方法,将foo作为this指针绑定到someuser.func,调用func()时,this指针为foo,所以输出结果是foo,而func2直接将绑定过的func赋值过来,与func行为完全相同

3使用 bind绑定参数表

bind方法还有一个重要功能,绑定参数表,如下例所示:

 1 var person = {
 2     name: 'by',
 3     says: function (act, obj) {
 4         console.log(this.name + ' ' + act + ' ' + obj);
 5     }
 6 };
 7 person.says('loves', 'diovyb'); //输出by loves diovyb
 8 
 9 bya = person.says.bind(person, 'loves');
10 bya('you'); //输出by loves you

  可以看到,bya将this指针绑定到了person,并将第一个参数绑定到loves,之后在调用bya的时候,只需传入第三个参数,这个特性可以用于创建一个函数的’捷径‘,之后我们可以通过这个捷径调用,以便在代码多处调用时省略重复输入相同的参数

4理解bind

尽管bind很优美,还是有一些令人疑惑的地方,例如下面的代码:

 1 var someuser = {
 2      name: 'by',
 3     func: function () {
 4         console.log(this.name);
 5     }
 6 };
 7 var foo = {
 8     name : 'foo'
 9 };
10 func = someuser.func.bind(foo);
11 func(); //输出foo
12 
13 func2 = func.bind(someuser);
14 func2(); //输出foo

全局函数func通过someuser.func.bind将this指针绑定到了foo,调用func()输出了foo,我们试图将func2赋值为已绑定的func重新通过bind将this指针绑定到someuser的结果,而调用func2时的输出却竟然没有像我们想像中的那样变成by,这是为森么呢,继续看下去,让我为你揭秘

Bind方法的简化版(不支持绑定参数表)

1 someuser.func.bind = function (self) {
2     return this.call(self);
3 };

   假设上面函数是someuser.func的bind方法的实现,函数体内的this指向的是someuser.func,因此函数也是对象,所以this.call(self)的作用就是以self作为this指针调用someuser.func

1 //将func = someuser.func.bind(foo) 展开
2 func = function () {
3     return someuser.func.call(foo);
4 };
5 //再将func2 = func.bind(someuser) 展开
6 func2 = function () {
7     return func.call(someuser);
8 };

从上面的展开过程我们可以看出,func2实际上是以 someuser 作为func的this 指针调用了func,而func根本没有使用this指针,所以二次bind是没有效果的。

A5原型

原型是js面向对象中重要特性

 1 function person() {
 2 
 3 }
 4 person.prototype.name = 'by';
 5 person.prototype.showName = function () {
 6     console.log(this.name);
 7 };
 8 
 9 var person = new person();
10 person.showName();

上面这段代码使用了原型而不是构造函数初始化对象,这样做与直接在构造函数中定义属性有什么不同呢?

1构造函数内定义的属性继承方式与原型不同,子对象需要显示调用父对象才能继承构造函数内部定义的属性

2.构造函数内定义的任何属性,包括函数在内都会被重复创建,同一个构造函数产生的二个对象不共享实例

3构造函数内定义的函数有运行时闭包的开销,因为构造函数内的局部变量对其中定义的函数来说是可见的

下面的这段代码可以验证以上问题:

 1 function foo() {
 2     var inner = 'hello';
 3     this.prop1 = 'by';
 4     this.func1 = function () {
 5         inner = '';
 6     };
 7 }
 8 foo.prototype.prop2 = 'car';
 9 foo.prototype.func2 = function () {
10     console.log(this.prop2);
11 };
12 var foo1 = new foo();
13 var foo2 = new foo();
14 console.log(foo1.func1 == foo2.func1); //输出false
15 console.log(foo1.func2 == foo2.func2); //输出 true

尽管如此,并不是说在构造函数内创建属性不好,而是二者各自有适合的范围,那么我们什么时候使用原型,什么时候使用构造函数来定义内部属性

1除非必须用构造函数闭包,否则尽量用原型定义成员函数,因为这样可以减少开销

2.尽量在构造函数内定义一般成员,尤其是对象或数组,因为用原型定义的成员是多个实例共享的

接下来,我们来介绍js的原型链机制

   js中有二个特殊的对象:object与function,它们都是构造函数,用于生成对象,object.prototype是所有对象的祖先,function.prototype是所有函数的原型,包括构造函数,我把js中的对象分为三类:一类用户创建的对象,一类是构造函数对象,一类是原型对象。用户创建的对象,即一般意义上用new语句显示构造的对象,构造函数对象指的是普通的构造函数,即通过new调用生成普通对象的函数,原型对象特指构造函数prototype属性指向的对象,这三类对象中的每一类都有一个__p[roto__属性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到object.prototype,构造函数对象有prototype属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的__proto__属性将会指向构造函数的prototype属性,原型对象有constructor属性,指向它对应的构造函数,让我们通过下面的例子来理解原型

 1 function foo() {
 2     
 3 }
 4 object.prototype.name = 'my';
 5 foo.prototype.name = 'bar';
 6 
 7 var obj = new object();
 8 var foo = new foo();
 9 console.log(obj.name); //输出my
10 console.log(foo.name); //输出bar
11 console.log(foo.__proto__.name); //输出bar
12 console.log(foo.__proto__.__proto__.name); //输出my
13 console.log(foo.__proto__.constructor.prototype.name); //输出bar

我们定义了一个叫做foo()的构造函数,生成对象foo.同时我们还分别给object和foo生成原型对象.

    在js中,继承是以依靠一套叫做原型链的机制实现的,属性继承的本质就是一个对象可以访问到它的原型链上任何一个原型对象的属性,例如上例的foo对象,它拥有foo.__Proto__和foo.__proto__.proto__所有属性的浅拷贝(只复制基本数据类型,不复制对象),所以可以直接访问foo.constructor(来自foo.__proto__,即foo.prototype),  foo.tostring(来自foo.__proto__.__proto__,即object.prototype).

A6对象的复制

js中没有像c语言一样的指针,也没有像java一样的clone方法可以进行对象赋值,因此我们需要手动实现这样一个函数,一个简单的做法就是复制对象的所有属性:

 1 object.prototype.clone = function () {
 2     var newobj = {};
 3     for (var i in this){
 4         newobj[i] = this[i];
 5     }
 6     return newobj;
 7 };
 8 var obj = {
 9     name: 'by',
10     likes:  ['node']
11 };
12 var newobj = obj.clone();
13 obj.likes.push('python');
14 
15 console.log(obj.likes); //输出['node','python']
16 console.log(newobj.likes); //输出['node','python']

 上面的代码是一个对象浅拷贝的实现,即只复制基本类型的属性,而共享对象类型的属性,浅拷贝的问题是二个对象共享对象类型的属性,例如上例中的likes属性指向的是同一个数组

  实现一个完全的复制,或深拷贝不是一件容易的事,因为除了基本数据类型,还有多种不同的对象,对象内部还有复杂的结构,因此需要用递归来实现

 1 object.prototype.clone = function () {
 2     var newobj = {};
 3     for (var i in this){
 4         if (typeof(this[i]) == 'object' || typeof(this[i] == 'function')){
 5             newobj[i] = this[i].clone();
 6         } else {
 7             newobj[i] = this[i];
 8         }
 9     }
10     return newobj;
11 };
12 Array.prototype.clone = function () {
13     var newArray = [];
14     for (var i = 0; i< this.length;i++){
15         if (typeof(this[i]) == 'object' || typeof(this[i] == 'function')){
16            newArray[i] = this[i].clone();
17         } else {
18             newArray[i] = this[i];
19         } 
20     }
21     return newArray;
22 };
23 function.prototype.clone = function () {
24     var that = this;
25     var newfunc = function(){
26         return that.apply(this, arguments);
27     };
28     for (var i in this){
29         newfunc[i] = this[i];
30     }
31     return newfunc();
32 };
33 var obj = {
34     name: 'by',
35     likes: ['node'],
36     display: function(){
37         console.log(this.name);
38     }
39 };
40 var newobj = obj.clone();
41 newobj.likes.push('python');
42 console.log(obj.likes); //输出['node']
43 console.log(newobj.likes); //输出['node','python']
44 console.log(obj.display == newobj.display); //输出false

上面这个办法实现虽然看上去非常完美,它不仅递归的复制了对象复杂的结构,还实现了函数的深拷贝,这个方法在大多数的情况都好用,但是有一种情况它无能为力,例如下面的代码

var obj1 = {ref: null
};
var obj2 = {ref: obj1
};
obj1.ref = obj2;

这段代码逻辑非常简单,就是二个相互引用的对象,当我们试图使用深拷贝来复制obj1和obj2中的任何一个时,问题就出现了,因为深拷贝的做法就是遇到对象就进行递归复制,那么结果只能无限循环下去,对于这种情况,简单的递归已经无法解决,必须设计一套图论算法,分析对象之间的依赖关系,建立一个拓扑结构图,然后分别依次复制每个顶点,并重新构建它们之间的依赖关系,这个我们暂且不讨论了,过于高深,而且在实际中我们也几乎不会遇到这种情况,好的,js全部重要的就在这里了,让我们下次再见!!!

 

转载于:https://www.cnblogs.com/237325670qqcom/p/5709095.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374291.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

带有NetBeans 7.1 RC 2的WebLogic 12c快速入门

WebLogic服务器12c停运了几天。 它是针对“裸露”的Java开发人员的–花哨的Fusion Middleware东西将继续沿线升至12c。 因此&#xff0c;这基本上是我要运行的版本。 今天&#xff0c;我为您提供了一个最新的NetBeans 7.1&#xff08;RC 2&#xff09;和WebLogic的快速入门 &am…

C学习杂记(四)sizeof计算联合体大小

#include <stdio.h>union u1 {char a[13];int b; };int main(void) {printf("%d\n", sizeof(u1));return 0; } 结果为16。 联合体的大小取决于它所有的成员中占用空间最大的一个成员的大小。u2最大的空间是char[13]&#xff0c;但是因为另一个成员int b的存在…

python爬虫反爬机制_Python Scrapy突破反爬虫机制(项目实践)

对于 BOSS 直聘这种网站&#xff0c;当程序请求网页后&#xff0c;服务器响应内容包含了整个页面的 HTML 源代码&#xff0c;这样就可以使用爬虫来爬取数据。但有些网站做了一些“反爬虫”处理&#xff0c;其网页内容不是静态的&#xff0c;而是使用 JavaScript 动态加载的&…

树的算法 已知二叉树的前序序列和中序序列求解树

题目: 已知二叉树的前序序列和中序序列求解树 比如 6 4    8 3  5   7 前序序列为6,4,3,5,8,7 中序序列为3,4,5,6,7,8 思路: 前序遍历序列的第一个元素必为根节点 则中序遍历序列中&#xff0c;该节点之前的为左子树&#xff0c;该节点之后的为右子树&#xff0c;若该节…

使用Spring配置LogBack日志记录

LogBack是由Log4j的同一作者创建的用于记录日志的API&#xff08;较新的实现&#xff0c;它类似于新版本&#xff09;&#xff0c;在本文中&#xff0c;我将展示如何在Spring项目中对其进行集成和使用。 在本教程中&#xff0c;我假设您正在使用一个简单的Spring ROO项目&…

自定义URL Scheme完全指南

iPhone / iOS SDK 最酷的特性之一就是应用将其自身”绑定”到一个自定义 URL scheme 上&#xff0c;该 scheme 用于从浏览器或其他应用中启动本应用。 注册自定义 URL Scheme 注册自定义 URL Scheme 的第一步是创建 URL Scheme — 在 Xcode Project Navigator 中找到并点击工程…

C学习杂记(五)形参实参笔试题

大意失荆州 不要以为简单就轻视&#xff0c;谨慎&#xff0c;细节&#xff0c;基础。 一、有以下程序 #include <stdio.h>typedef struct {int b, p;} A;void f(A c) {c.b 1; c.p 2; }void main(void) {A a {1, 2};f(a);printf("%d, %d\n", a.b, a.p); } …

avframe转byte数组_C# amp; VB6.0 图像与二维数组 互转

背景最近在研究C#进行图像处理&#xff0c;在图像处理中算法中&#xff0c;往往都是针对的是矩阵运算的。矩阵其实就是一个二维的数组。为了图像处理的速度&#xff0c;我们都需要放在内存中处理。但网络上收集的代码&#xff0c;往往都是以一维数组的样子提供结果&#xff0c;…

C学习杂记(六)%2.0f打印输出宽度

%m.nf&#xff0c;m表示整个浮点数的输出宽度&#xff0c;n表示小数输出宽度。 1、printf("%f\n", 12.34); 输出为12.340000。 2、printf("%2.0f\n", 12.34); 输出为12。 3、printf("%2.1f\n", 12.34); 输出为12.3。 4、printf(&qu…

P6 音频格式—— AAC

目录 前言 01 AAC是什么&#xff1f; 02 为什么需要进行AAC进行音频压缩处理&#xff1f; 03 AAC的特点以及优势 04 AAC格式详解&#xff1a; 4.1. ADIF的数据结构&#xff1a; 4.1.1 ADIF Header具体的表格: 4.2. ADTS的结构&#xff08;重点&#xff09;&#xff1a; …

Android开发笔记——ListView模块、缓存及性能

ListView是Android开发中最常用的组件之一。本文将重点说明如何正确使用ListView&#xff0c;以及使用过程中可能遇到的问题。 ListView开发模块图片缓存可能遇到的问题一、ListView开发模块 从项目实践的角度来看&#xff0c;ListView适合“自底向上”的开发模式&#xff0c;即…

python实现excel筛选功能并输出_python如何实现excel按颜色筛选功能

离岛 2020-07-09 09:37 已采纳 不太了解具体需求&#xff0c;提供一些示例代码和思路供你参考&#xff1a; 整体思路&#xff1a;首先已知excel中的颜色值&#xff0c;根据编码实现颜色筛选的功能 示例&#xff1a; 1、首先安装pip install openpyxl 2、示例代码可以获取Excel中…

什么是CDI,它与@EJB和Spring有什么关系?

简要概述了Java EE中的依赖项注入&#xff0c; Resource / EJB和Inject之间的区别以及它们与Spring的关系-主要是链接形式。 上下文依赖注入&#xff08;CDI&#xff0c; JSR 299 &#xff09;是Java EE 6 Web Profile的一部分&#xff0c;它本身基于Java依赖注入&#xff08;…

C学习杂记(七)extern声明可省略变量类型

工作三年&#xff0c;看C的书也不少。第一次知道extern可以省略变量类型。 b.c有一个全局变量unsigned int data_length&#xff0c;a.c想要调用它&#xff0c;通常使用: extern unsigned int data_length&#xff1b; 在声明时可以把外部变量类型去掉&#xff1a;extern da…

KMP模板

1 ///KMP模板2 ///生成next数组3 void get_next()4 {5 int i0,j-1;6 next[0]-1;7 while (s1[i])8 {9 if (j-1||s1[i]s1[j]) 10 { 11 i; 12 j; 13 next[i]j; 14 } 15 else jnext[j]; 16 …

使用Apache CXF进行Web服务学习

在我的最后几个项目中&#xff0c;我使用了Web服务&#xff0c;在某些地方创建它们并在其他地方使用它们。 我觉得创建客户端&#xff0c;创建Web服务等标准任务非常简单&#xff0c;如果遇到问题&#xff0c;有足够的资源。 但是对于Web服务&#xff0c;这是一项琐碎的任务&am…

python的easygui_Python的easygui学习

1.调用方法 &#xff08;1&#xff09;import easygui easygui.msgbox(…) &#xff08;2&#xff09;from easygui import msgbox(…) 2.函数方法 import easygui a easygui.msgbox(’…’, title‘title’) # show a:返回ok,none b easygui.enterbox( ‘plaese give a solu…

c#递归

一种算法&#xff0c;通过简洁的语句定义无限集合、函数或者子程序在运行时直接或间接调用自身产生重入的现象。 特点&#xff1a;递归算法分递推&#xff08;简单到复杂的推理过程&#xff09;和回归&#xff08;获得简单解后逐级返回得到复杂的解&#xff09;2个阶段。 可理解…

HDU5724

题意&#xff1a; 一个 n * 20 的棋盘&#xff0c;棋盘上有若干棋子&#xff0c;Alice 和 Bob 轮流走&#xff0c;每人每次可以选择任一行的一颗棋子向右移动到最近的一个空格 &#xff1b;也就是说如果右边与它相邻的格子里没有棋子&#xff0c;就移到右边与他相邻的格子去&am…

C语言代码规范(九)运算符优先级使用括号提高阅读性

举简单例子 a b | c << d 2; 对于大牛没有问题&#xff0c;对于我这样的码农需要思考一下运算优先级 对于这种情况华某有规范使用括号来表示运算顺序&#xff0c;从而提高代码可阅读性 a b | ( c << (d 2) ); 这样一目了然&#xff0c;大家好才是真的好。…