资讯专栏INFORMATION COLUMN

原生javascript实现extend

Steve_Wang_ / 3309人阅读

摘要:使用会遍历所有的可枚举属性,包括原型。所以需要判断一下,是否是对象自身的属性,而不是继承于原型的。注意,会跳过那些值为或的源对象。

代码
var obj1 = {"a": "obj2","b":"2"};
var obj2 = {name: "obj3"};
function extend() {
    var length = arguments.length;
    var target = arguments[0] || {};
    if (typeof target!="object" && typeof target != "function") {
        target = {};
    }
    if (length == 1) {
        target = this;
        i--;
    }
    for (var i = 1; i < length; i++) { 
        var source = arguments[i]; 
        for (var key in source) { 
            // 使用for in会遍历数组所有的可枚举属性,包括原型。
            if (Object.prototype.hasOwnProperty.call(source, key)) { 
                target[key] = source[key]; 
            } 
        } 
    }
    return target; 
}
console.log(extend(obj1,obj2));

extend 要实现的是给任意对象扩展
分析一下

在extend()函数中没有写死参数,是为了更好的扩展性,永远也不知道需要扩展的对象有几个。
而是通过arguments来获取传进来的参数。

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。
// 可以转换为数组 ES2015
const args = Array.from(arguments);

console.log(typeof arguments); // "object"
target

target是传进来的第一个参数,也就是需要扩展的对象。

var target = arguments[0] || {}; // 如果没有传参,则设为一个空对象

// 进行这一步判断是为了保证代码的可执行性,如果传进来的是个数字、布尔值,则设为一个空对象
if (typeof target!="object" && typeof target != "function") {
    target = {};
}
循环遍历赋值
for (var i = 1; i < length; i++) { 
    var source = arguments[i]; 
    for (var key in source) { 
        // 使用for in会遍历数组所有的可枚举属性,包括原型。
        if (Object.prototype.hasOwnProperty.call(source, key)) { 
            target[key] = source[key]; 
        } 
    } 
}

这一步就是将扩展源里的属性、方法循环遍历赋值到扩展项中。

如果扩展项和扩展源中有相同的属性、方法,后面的会覆盖前面的。 这个思想也是插件开发中,实现用户配置覆盖默认设置的实现思想。
hasOwnProperty

为什么需要使用hasOwnProperty,这跟for in有密切关系。

使用for in会遍历所有的可枚举属性,包括原型。

所以需要判断一下,是否是对象自身的属性,而不是继承于原型的。

那为什么不直接使用source.hasOwnProperty(source[key])呢?

JavaScript 并没有保护 hasOwnProperty 属性名,因此某个对象是有可能存在使用这个属性名的属性,使用外部的 hasOwnProperty 获得正确的结果是需要的:

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: "Here be dragons"
};

foo.hasOwnProperty("bar"); // 始终返回 false

// 如果担心这种情况,可以直接使用原型链上真正的 hasOwnProperty 方法
({}).hasOwnProperty.call(foo, "bar"); // true

// 也可以使用 Object 原型上的 hasOwnProperty 属性
Object.prototype.hasOwnProperty.call(foo, "bar"); // true
call apply

上面用到的call和apply,就在这里记录一下。

1.每个函数都包含两个非继承而来的方法:call()方法和apply()方法。
2.相同点:这两个方法的作用是一样的。
都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。
一般来说,this总是指向调用某个方法的对象,但是使用call()和apply()方法时,就会改变this的指向。
3.不同点:接收参数的方式不同。

apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。

语法:apply([thisObj [,argArray] ]);,调用一个对象的一个方法,2另一个对象替换当前对象。
说明:如果argArray不是一个有效数组或不是arguments对象,那么将导致一个TypeError,如果没有提供argArray和thisObj任何一个参数,那么Global对象将用作thisObj。

call()方法 第一个参数和apply()方法的一样,但是传递给函数的参数必须列举出来。

语法:call([thisObject[,arg1 [,arg2 [,...,argn]]]]);,应用某一对象的一个方法,用另一个对象替换当前对象。
说明: call方法可以用来代替另一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始的上下文改变为thisObj指定的新对象,如果没有提供thisObj参数,那么Global对象被用于thisObj。

// call
    window.name = "FinGet";
    document.name = "FinGet1";

    var boy = {name: "FinGet2" };
    function showName(){
        console.log(this.name);
    }

    showName.call();         //FinGet (默认传递参数)  this 是指向window
    showName.call(window);   //FinGet
    showName.call(document); //FinGet1
    showName.call(this);     //FinGet
    showName.call(boy);       //FinGet2

    var Pet = {
        words : "hello",
        speak : function (say) {
            console.log(say + ""+ this.words)
        }
    }
    Pet.speak("Speak"); // 结果:Speakhello

    var Dog = {
        words:"Wang"
    }

    //将this的指向改变成了Dog
    Pet.speak.call(Dog, "Speak"); //结果: SpeakWang

可以将上面代码中的call换成apply,也是可以执行的。

Object.assign
Object.assign(target, ...sources)

target 目标对象

sources 源对象

如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
注意,Object.assign 会跳过那些值为 null 或 undefined 的源对象。
var obj1 = {a:"1",b:"2"};
var obj2 = {c:"3",d:"4"};
Object.assign(obj1,obj2); // Object {a: "1", b: "2", c: "3", d: "4"}

obj1 也改变
Object {a: "1", b: "2", c: "3", d: "4"}

obj2
Object {c: "3", d: "4"}

更多相关Object.assign可以查看官网。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94139.html

相关文章

  • JavaScript基础: 类与继承

    摘要:类的方法相当于之前我们定义在构造函数的原型上。的构造函数中调用其目的就是调用父类的构造函数。是先创建子类的实例,然后在子类实例的基础上创建父类的属性。 前言   首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。    许久已经没有写东西了,因为杂七杂八的原因最近一直没有抽出时间来把写作坚持下来,感觉和跑步一...

    liuchengxu 评论0 收藏0
  • 浅谈React事件机制

    摘要:事件简介事件是合成事件,所有事件都自动绑定到最外层上。支持事件的冒泡机制,我们可以使用和来中断它。这样做简化了事件处理和回收机制,效率也有很大提升。事件类型合成事件的事件类型是原生事件类型的一个子集。 React事件简介 React事件是合成事件,所有事件都自动绑定到最外层上。因为Virtual DOM 在内存中是以对象的形式存在的,所以React 基于 Virtual DOM 实现了...

    moven_j 评论0 收藏0
  • 浅谈React事件机制

    摘要:事件简介事件是合成事件,所有事件都自动绑定到最外层上。支持事件的冒泡机制,我们可以使用和来中断它。这样做简化了事件处理和回收机制,效率也有很大提升。事件类型合成事件的事件类型是原生事件类型的一个子集。 React事件简介 React事件是合成事件,所有事件都自动绑定到最外层上。因为Virtual DOM 在内存中是以对象的形式存在的,所以React 基于 Virtual DOM 实现了...

    MyFaith 评论0 收藏0
  • 从Vue数组响应化所引发的思考

    摘要:因为无法通过借用构造函数的方式创建响应式属性虽然属性可以被创建,但不具备响应式功能,因此在我们是没法继承数组的。上面整个的文章都是基于监听数组响应的一个点想到的。 前言   首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。   从上一篇文章响应式数据与数据依赖基本原理开始,我就萌发了想要研究Vue源码的想法...

    hikui 评论0 收藏0
  • JavaScript 工作原理之十五-类和继承及 Babel 和 TypeScript 代码转换探秘

    摘要:使用新的易用的类定义,归根结底也是要创建构造函数和修改原型。首先,它把构造函数当成单独的函数且包含类属性集。该节点还储存了指向父类的指针引用,该父类也并储存了构造函数,属性集和及父类引用,依次类推。 原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第...

    GeekGhc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<