JavaScript 复制对象

by admin on 2018年11月17日

原文链接:http://www.ha97.com/4617.html

当JavaScript这门语言中,数据类型分为两异常接近:基本数据类和复杂数据列。基本数据类包括Number、Boolean、String、Null、String、Symbol(ES6
新增),而复杂数据类包括Object,而有其他引用类型(Array、Date、RegExp、Function、基本包装档次(Boolean、String、Number)、Math等)都是Object类型的实例对象,因此还好继承Object原型对象的有的性能与方式。

 参考博文:apache修改最深连接并用ab网站压力测试
 http://www.blogjava.net/gf7/archive/2012/12/20/393255.html

假如对核心数据类来说,复制一个变量值,本质上虽是copy了这个变量。一个变量值的修改,不会见潜移默化至另外一个变量。看一个简短的例证。

 

let val = 123;
let copy = val;
console.log(copy);  //123
val = 456;          //修改val的值对copy的值不产生影响
console.log(copy);  //123

PS:网站性能压力测试是性调优过程遭到不可或缺的平环抱。只有让服务器处在高压状态下才确实体现出各种设置所暴露的题材。Apache被来只自带的,名吧ab的次第,可以本着Apache或者外类型的服务器进行网站访问压力测试。

只要对复杂数据类来说,同基本数据类实现之匪极端一致。对于复杂数据类的复制,要留心的是,变量名仅是借助为此目标的指针。当我们将保存对象的一个变量赋值给其它一个变量时,实际上复制的凡此指针,而少于独变量都指向都一个对象。因此,一个目标的改动,会潜移默化及另外一个靶。

ApacheBench命令原理:

ab命令会创建很多底起访问线程,模拟多只访问者同时针对某平等URL地址进行走访。它的测试目标是依据URL的,因此,既可以据此来测试Apache的负载压力,也足以测试nginx、lighthttp、tomcat、IIS等另外Web服务器的压力。

ab命令对发负载的处理器要求大没有,既非会见占据很高CPU,也无见面占多内存,但也会为目标服务器造成巨大的负荷,其原理类似CC攻击。自己测试用也须留意,否则一律涂鸦上无限多的负荷,可能引致目标服务器因为资源消耗完,严重时居然招致死机。

// obj只是指向对象的指针
let obj = {
    character: 'peaceful'
};
//copy变量复制了这个指针,指向同一个对象
let copy = obj;
console.log(copy);          //{character: 'peaceful'}
obj.character = 'lovely';
console.log(copy);          //{character: 'lovely'} 

ApacheBench参数说明

格式:ab [options] [http://]hostname[:port]/path
参数说明:
-n requests Number of requests to perform
//在测试会话中所执行的请求个数(本次测试总共要访问页面的次数)。默认时,仅执行一个请求。
-c concurrency Number of multiple requests to make
//一次产生的请求个数(并发数)。默认是一次一个。
-t timelimit Seconds to max. wait for responses
//测试所进行的最大秒数。其内部隐含值是-n 50000。它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。
-p postfile File containing data to POST
//包含了需要POST的数据的文件,文件格式如“p1=1&p2=2”.使用方法是 -p 111.txt 。 (配合-T)
-T content-type Content-type header for POSTing
//POST数据所使用的Content-type头信息,如 -T “application/x-www-form-urlencoded” 。 (配合-p)
-v verbosity How much troubleshooting info to print
//设置显示信息的详细程度 – 4或更大值会显示头信息, 3或更大值可以显示响应代码(404, 200等), 2或更大值可以显示警告和其他信息。 -V 显示版本号并退出。
-w Print out results in HTML tables
//以HTML表的格式输出结果。默认时,它是白色背景的两列宽度的一张表。
-i Use HEAD instead of GET
// 执行HEAD请求,而不是GET。
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. -C “c1=1234,c2=2,c3=3″ (repeatable)
//-C cookie-name=value 对请求附加一个Cookie:行。 其典型形式是name=value的一个参数对。此参数可以重复,用逗号分割。
提示:可以借助session实现原理传递 JSESSIONID参数, 实现保持会话的功能,如
-C ” c1=1234,c2=2,c3=3, JSESSIONID=FF056CD16DA9D71CB131C1D56F0319F8″ 。
-H attribute Add Arbitrary header line, eg. ‘Accept-Encoding: gzip’ Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
//-P proxy-auth-username:password 对一个中转代理提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即, 是否发送了401认证需求代码),此字符串都会被发送。
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-h Display usage information (this message)
//-attributes 设置属性的字符串. 缺陷程序中有各种静态声明的固定长度的缓冲区。另外,对命令行参数、服务器的响应头和其他外部输入的解析也很简单,这可能会有不良后果。它没有完整地实现 HTTP/1.x; 仅接受某些’预想’的响应格式。 strstr(3)的频繁使用可能会带来性能问题,即你可能是在测试ab而不是服务器的性能。

参数很多,一般我们为此 -c 和 -n 参数就好了。例如:

# ab -c 5000 -n 600 http://127.0.0.1/index.php

发出雷同合乎很像之觊觎描述了复杂数据类复制的规律
图片 1
同理,在复制一个数组时,变量名仅是靠于者数组对象的指针;在复制一个函数时,函数名为单纯是因为此函数对象的指针

ApacheBench用法详解:

在Linux系统,一般安装好Apache后好一直实施;
# ab -n 4000 -c 1000 http://www.ha97.com/

倘若是Win系统下,打开cmd命令行窗口,cd到apache安装目录的bin目录下;

-n后面的4000意味着一起有4000单请求;-c后面的1000表示以1000只冒出(模拟1000民用而做客),后面的网址表示测试的靶子URL。

稍许等一样会晤得到近似如下显示结果:
图片 2

结果分析:

This is ApacheBench, Version 2.3
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.80.157 (be patient)
Completed 400 requests
Completed 800 requests
Completed 1200 requests
Completed 1600 requests
Completed 2000 requests
Completed 2400 requests
Completed 2800 requests
Completed 3200 requests
Completed 3600 requests
Completed 4000 requests
Finished 4000 requests

Server Software: Apache/2.2.15
Server Hostname: 192.168.80.157
Server Port: 80

Document Path: /phpinfo.php
#测试的页面
Document Length: 50797 bytes
#页面大小

Concurrency Level: 1000
#测试的并发数
Time taken for tests: 11.846 seconds
#整个测试持续的时间
Complete requests: 4000
#完成的请求数量
Failed requests: 0
#失败的请求数量
Write errors: 0
Total transferred: 204586997 bytes
#整个过程中的网络传输量
HTML transferred: 203479961 bytes
#整个过程中的HTML内容传输量
Requests per second: 337.67 [#/sec] (mean)
#最重要的指标之一,相当于LR中的每秒事务数,后面括号中的mean表示这是一个平均值
Time per request: 2961.449 [ms] (mean)
#最重要的指标之二,相当于LR中的平均事务响应时间,后面括号中的mean表示这是一个平均值
Time per request: 2.961 [ms] (mean, across all concurrent requests)
#每个连接请求实际运行时间的平均值
Transfer rate: 16866.07 [Kbytes/sec] received
#平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 483 1773.5 11 9052
Processing: 2 556 1459.1 255 11763
Waiting: 1 515 1459.8 220 11756
Total: 139 1039 2296.6 275 11843
#网络上消耗的时间的分解,各项数据的具体算法还不是很清楚

Percentage of the requests served within a certain time (ms)
50% 275
66% 298
75% 328
80% 373
90% 3260
95% 9075
98% 9267
99% 11713
100% 11843 (longest request)
#整个场景中所有请求的响应情况。在场景中每个请求都有一个响应时间,其中50%的用户响应时间小于275毫秒,66%的用户响应时间小于298毫秒,最大的响应时间小于11843毫秒。对于并发请求,cpu实际上并不是同时处理的,而是按照每个请求获得的时间片逐个轮转处理的,所以基本上第一个Time per request时间约等于第二个Time per request时间乘以并发请求数。

总结:在长途对web服务器进行压力测试,往往效果不帅(因为网络延时过十分),建议采取内网的别一样台抑多华服务器通过内网进行测试,这样得出的数码,准确度会强多。如果只有单独的同一宝服务器,可以直接本地测试,比远程测试效果使标准。

let arr = [1, 2, 3];
let copy = arr;
console.log(copy); // [1, 2, 3]
arr[0] = 'keith';
console.log(copy); // 数组对象被改变: ['keith', 2, 3]
arr = null;
console.log(copy); // ['keith', 2, 3] 即使arr=null,也不会影响copy。因此此时的arr变量只是一个指向数组对象的指针

function foo () {
    return 'hello world';
};
let bar = foo;
console.log(foo());
foo = null;     //foo只是指向函数对象的指针
console.log(bar());

故而,我们应当怎么兑现目标的浓度复制?

复制对象

以JavaScript中,复制对象分为两栽方法,浅复制和深复制。

浅复制没有辙去真正的去复制一个对象,而只是保存了针对拖欠目标的援;而雅复制可实现真正的复制一个对象。

浅复制

以ES6遇,Object对象新增了一个assign方法,可以实现目标的浅复制。这里谈谈Object.assign方法的有血有肉用法,因为稍后会分析jQuery的extend方法,实现之规律同Object.assign方法大多

Object.assign的第一个参数是目标对象,可以同同样或多只自对象作为参数,将源对象的所有可枚举([[emuerable]]

true)复制到对象对象。这种复制属于浅复制,复制对象时不过是含对该对象的援。Object.assign(target, [source1, source2, ...])

  • 假设目标对象及自对象来同名属性,则后面的属于性会覆盖前的性能
  • 要是一味生一个参数,则直接返回该参数。即Object.assign(obj) === obj
  • 比方第一独参数不是目标,而是基本数据类(Null、Undefined除外),则会调用对应之为主包装档次
  • 设第一个参数是Null和Undefined,则会报错;如果Null和Undefined不是置身第一只参数,则会聊过该参数的复制

使落实目标的浅复制,可以使用Object.assign方法

let target = {a: 123};
let source1 = {b: 456};
let source2 = {c: 789};
let obj = Object.assign(target, source1, source2);
console.log(obj);

只是对深复制来说,Object.assign方法无法落实

let target = {a: 123};
let source1 = {b: 456};
let source2 = {c: 789, d: {e: 'lovely'}};
let obj = Object.assign(target, source1, source2);
source2.d.e = 'peaceful';
console.log(obj);   // {a: 123, b: 456, c: 789, d: {e: 'peaceful'}}

从者代码中好看看,source2对象被e属性的改动,仍然会潜移默化及obj对象

深复制

于实际的开发项目中,前后端进行数据传,主要是透过JSON实现之。JSON全称:JavaScript
Object Notation,JavaScript对象表示拟。

JSON对象下产生个别只措施,一凡拿JS对象转换成为字符串对象的JSON.stringify方法;一个凡是将字符串对象转换成JS对象的JSON.parse方法。

立刻简单个点子结合使用可以实现目标的深复制。也就是说,当我们要复制一个obj对象时,可以先调用JSON.stringify(obj),将该转移为字符串对象,然后再调用JSON.parse方法,将那易为JS对象。就可以轻松的贯彻目标的深复制

let obj = {
    a: 123,
    b: {
        c: 456,
        d: {
            e: 789
        }
    }
};
let copy = JSON.parse(JSON.stringify(obj));
// 对obj对象无论怎么修改,都不会影响到copy对象
obj.b.c = 'hello';
obj.b.d.e = 'world';
console.log(copy);  // {a: 123, b: {c: 456, d: {e: 789}}}

自然,使用这种艺术贯彻深复制有一个败笔就是必为JSON.parse方法传入的字符串必须是官方的JSON,否则会丢掉来错误

jQuery.extend || jQuery.fn.extend

jQuery.extend对象,对下jQuery超过一定时间的情人吧并无默认。这个$.extend方法可为此来扩张jQuery的全局对象,而$.fn.extend方法可以就此来扩大实例对象。fn实际上是prototype对象的号,所以,扩展实例对象的章程其实即便是当jQuery原型对象及加加有道。

$.extend方法不但可以为此来写jQuery插件,同样的,它可就此来兑现目标的浓度复制。(使用$.extend与$.fn.extend实现深浅复制都得以,唯一的差异就是this的指向性不同)

于具体分析源代码之前,我在源码中看到底$.extend方法的组成部分特征

  • 当不收受外参数时,直接返回一个缺损对象
  • 当只有发一个参数时(这个参数可以外数据类型(Null、Undefined、Boolean、String、Number、Object)),会回来this对象,这里见面分为两种植状况。如果用$.extend,会回jQuery对象;如果就此$.fn.extend,会回到jQuery的原型对象。
  • 当接受两单参数时,并且第一只参数是Boolean值时,也会见回到一个拖欠对象。如果第一个参数不是Boolean值,那么会以自对象复制到目标靶
  • 当收到三只参数以上时,可以分成两种植状态。如果第一个参数是Boolean值表示深浅复制,那么目标靶会移动到第二只参数,源对象见面活动至第三个参数。(目标靶、源对象及Object.assign方法吃的同等)。如果第一只参数不是Boolean值,那么因此法及Object.assign方法常规的复制相同。
  • 以循环源对象的历程遭到,任何数据类型为Null、Undefined或者来对象是一个拖欠对象时,在复制的进程遭到还见面叫忽略。
  • 万一来对象和对象对象有同名的习性,则出自对象的属于性会覆盖掉目标对象中之属性。如果跟名属性是一个目标的语,则会在deep=true等任何条件下往目标对象的欠同名对象上加属性

下面贴出jQuery-2.1.4中jQuery.extend实现方式的源代码

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[0] || {},
        // 使用||运算符,排除隐式强制类型转换为false的数据类型
        // 如'', 0, undefined, null, false等
        // 如果target为以上的值,则设置target = {}
        i = 1,
        length = arguments.length,
        deep = false;

    // 当typeof target === 'boolean'时
    // 则将deep设置为target的值
    // 然后将target移动到第二个参数,
    if (typeof target === "boolean") {
        deep = target;
        // 使用||运算符,排除隐式强制类型转换为false的数据类型
        // 如'', 0, undefined, null, false等
        // 如果target为以上的值,则设置target = {}
        target = arguments[i] || {};
        i++;
    }

    // 如果target不是一个对象或数组或函数,
    // 则设置target = {}
    // 这里与Object.assign的处理方法不同,
    // assign方法会将Boolean、String、Number方法转换为对应的基本包装类型
    // 然后再返回,
    // 而extend方法直接将typeof不为object或function的数据类型
    // 全部转换为一个空对象
    if (typeof target !== "object" && !jQuery.isFunction(target)) {
        target = {};
    }

    // 如果arguments.length === 1 或
    // typeof arguments[0] === 'boolean', 且存在arguments[1],
    // 这时候目标对象会指向this
    // this的指向哪个对象需要看是使用$.fn.extend还是$.extend
    if (i === length) {
        target = this;
        // i-- 表示不进入for循环
        i--;
    }

    // 循环arguments类数组对象,从源对象开始
    for (; i < length; i++) {
        // 针对下面if判断
        // 有一点需要注意的是
        // 这里有一个隐式强制类型转换 undefined == null 为 true
        // 而undefined === null 为 false
        // 所以如果源对象中数据类型为Undefined或Null
        // 那么就会跳过本次循环,接着循环下一个源对象
        if ((options = arguments[i]) != null) {
            // 遍历所有[[emuerable]] === true的源对象
            // 包括Object, Array, String
            // 如果遇到源对象的数据类型为Boolean, Number
            // for in循环会被跳过,不执行for in循环
            for (name in options) {
                // src用于判断target对象是否存在name属性
                src = target[name];

                // 需要复制的属性
                // 当前源对象的name属性
                copy = options[name];

                // 这种情况暂时未遇到..
                // 按照我的理解,
                // 即使copy是同target是一样的对象
                // 两个对象也不可能相等的..
                if (target === copy) {
                    continue;
                }

                // if判断主要用途:
                // 如果是深复制且copy是一个对象或数组
                // 则需要递归jQuery.extend(),
                // 直到copy成为一个基本数据类型为止
                if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
                    // 深复制
                    if (copyIsArray) {
                        // 如果是copy是一个数组
                        // 将copyIsArray重置为默认值
                        copyIsArray = false;
                        // 如果目标对象存在name属性且是一个数组
                        // 则使用目标对象的name属性,否则重新创建一个数组,用于复制
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        // 如果目标对象存在name属性且是一个对象
                        // 则使用目标对象的name属性,否则重新创建一个对象,用于复制
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    // 因为深复制,所以递归调用jQuery.extend方法
                    // 返回值为target对象,即clone对象
                    // copy是一个源对象
                    target[name] = jQuery.extend(deep, clone, copy);

                } else if (copy !== undefined) {
                    // 浅复制
                    // 如果copy不是一个对象或数组
                    // 那么执行elseif分支
                    // 在elseif判断中如果copy是一个对象或数组,
                    // 但是都为空的话,排除这种情况
                    // 因为获取空对象的属性会返回undefined
                    target[name] = copy;
                }
            }
        }
    }

    // 当源对象全部循环完毕之后,返回目标对象
    return target;
};      

之所以,可以针对分析后之源码,给起一部分例子

let obj1 = $.extend();
console.log(obj1); // 返回一个空对象 {}

let obj2 = $.extend(undefined);
console.log(obj2); //返回jQuery对象,Object.assign传入undefined会报错

let obj3 = $.extend('123');
console.log(obj3); // 返回jQuery对象,Object.assign传入'123'会返回字符串的String对象

let target = {
    a: 123,
    b: 234
};

let source1 = {
    b: 456,
    d: ['keith', 'peaceful', 'lovely']
};

let source2 = {c: 789};
let source3 = {};

let obj4 = $.extend(target, source1, source2);
// let obj4 = $.extend(false, target, source1, source2);
console.log(obj4); // {a: 123, b: 456, d: Array(3), c: 789}
// 默认情况下,复制方式都是浅复制
// 如果只需要浅复制,不传入deep参数也可以
// 浅复制时,obj4对象中的d属性只是指向数组对象的指针

let obj5 = $.extend(target, undefined, source2);
let obj6 = $.extend(target, source3, source2);
console.log(obj5, obj6);
// {a: 123, b: 234, c: 789}, {a: 123, b: 234, c: 789}
// 会略过空对象或Undefined、Null值

let obj7 = $.extend(true, target, source1, source2);
console.log(obj7);  // {a: 123, b: 456, d: Array(3), c: 789}
// 这里target对象有b属性,源对象source1也有b属性
// 此时源对象的b属性会覆盖目标对象的b属性
// 这里deep=true,属于深复制
// 当name=d时,会递归调用$.extend, 直到它的属性对应的属性值全部为基本数据类型
// 源对象的改变不会影响到obj7对象

JavaScript 复制对象

因而,可以依据$.extend方法,写来一个通用的贯彻目标深浅复制的函数,copyObject函数唯一的两样就是当i
=== arguments.length属性时,copyObject函数直接归了target对象

function copyObject () {
    let i = 1,
        target = arguments[0] || {},
        deep = false,
        length = arguments.length,
        name, options, src, copy,
        copyIsArray, clone;

    // 如果第一个参数的数据类型是Boolean类型
    // target往后取第二个参数
    if (typeof target === 'boolean') {
        deep = target;
        // 使用||运算符,排除隐式强制类型转换为false的数据类型
        // 如'', 0, undefined, null, false等
        // 如果target为以上的值,则设置target = {}
        target = arguments[1] || {};
        i++;
    }

    // 如果target不是一个对象或数组或函数
    if (typeof target !== 'object' && !(typeof target === 'function')) {
        target = {};
    }

    // 如果arguments.length === 1 或
    // typeof arguments[0] === 'boolean',
    // 且存在arguments[1],则直接返回target对象
    if (i === length) {
        return target;
    }

    // 循环每个源对象
    for (; i < length; i++) {
        // 如果传入的源对象是null或undefined
        // 则循环下一个源对象
        if (typeof (options = arguments[i]) != null) {
            // 遍历所有[[emuerable]] === true的源对象
            // 包括Object, Array, String
            // 如果遇到源对象的数据类型为Boolean, Number
            // for in循环会被跳过,不执行for in循环
            for (name in options) {
                // src用于判断target对象是否存在name属性
                src = target[name];
                // copy用于复制
                copy = options[name];
                // 判断copy是否是数组
                copyIsArray = Array.isArray(copy);
                if (deep && copy && (typeof copy === 'object' || copyIsArray)) {
                    if (copyIsArray) {
                        copyIsArray = false;
                        // 如果目标对象存在name属性且是一个数组
                        // 则使用目标对象的name属性,否则重新创建一个数组,用于复制
                        clone = src && Array.isArray(src) ? src : [];
                    } else {
                        // 如果目标对象存在name属性且是一个对象
                        // 则使用目标对象的name属性,否则重新创建一个对象,用于复制
                        clone = src && typeof src === 'object' ? src : {};
                    }
                    // 深复制,所以递归调用copyObject函数
                    // 返回值为target对象,即clone对象
                    // copy是一个源对象
                    target[name] = copyObject(deep, clone, copy);
                } else if (copy !== undefined){
                    // 浅复制,直接复制到target对象上
                    target[name] = copy;
                }
            }
        }
    }
    // 返回目标对象
    return target;      
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图