从了解new出发,手写代码(1)

3,272 阅读4分钟

什么是new?

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例

new做了什么?

  1. 创建一个空的简单JavaScript对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this。

new的实现过程

读懂我的代码

其实我们了解了new的整个过程我们是比较容易去实现一个new的 1.let obj = {} 或者 let obj = new object(); 两者等价 2.obj.proto = Func.prototype (Func为构造函数) 3.通过apply改变this,以访问函数内部变量 4.返回

function New() {
    let obj = {}; // 创建对象
    console.log(arguments);
    let constructor =  [].shift.call(arguments); // 获取构造函数
    console.log(arguments);
    if (constructor.prototype !== null) {
        obj.__proto__ = constructor.prototype; // 构造函数链接到新对象
    }
    let ret = constructor.apply(obj, [].slice.call(arguments)); // 改变this指向
    console.log(arguments);
    console.log(typeof ret);
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return obj; // 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。
}

function name(a, b) {
	this.a = a;
	this.b = b;
}

let c = New(name, 1, 2)
let d = new name(1, 2)
console.log(c);
console.log(d);

我们来看返回值:

我们看到New 和 new产生了同样的效果

注意:[].shift.slice()会改变我们的arguments。typeof null == "object"

重点:为什么要进行对象判断,一般实现new时会把这一点的解释给忽略。

默认情况下函数的返回值为undefined(即没有显示地定义返回值的话),但是构造函数比较例外,new构造函数在没有return的情况下默认返回新创建的对象。但是在有显示返回值的情况下,如果返回值为基本数据类型的话(string,number,boolean,undefined,null),返回值仍然为新创建的对象,这一点比较奇怪,需要注意。只有在显示返回一个非基本数据类型的对象的时候,函数的返回值才为指定的对象。在这种情况下,this值所引用的对象就被丢弃了。

看下面两个例子:

例1:

例2:

// return; // 返回 this

// return null; // 返回 this

// return this;

// return []; // 返回 []

// return function(){}; // 返回 这个 function,抛弃 this

// return false; // 返回 this

// return new Boolean( false); // 返回新 boolean;抛弃 this

// return 'hello world'; // 返回 this

// return new String( 'hello world'); // 返回 新建的 string,抛弃 this

// return 2; // 返回 this

// return new Number( 32); // 返回新的 number,抛弃 this

arguments是什么?

arguments 是一个对应于传递给函数的参数的类数组对象。

arguments 对象只能在函数内使用

[].slice.call()做了什么?

将arguments转换成数组

类似的转换方法

var args = Array.prototype.slice.call(arguments);

var args = [].slice.call(arguments);

// ES2015

const args = Array.from(arguments);

const args = [...arguments];

MDN上不建议我们对参数进行slice 解决方案:

function New() {
    let obj = {}; // 创建对象
    console.log(arguments);
    let constructor =  [].shift.call(arguments); // 获取构造函数
    console.log(arguments);
    if (constructor.prototype !== null) {
        obj.__proto__ = constructor.prototype;
    }
    let ret = constructor.apply(obj, (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments)));
    console.log(ret);
    console.log(arguments);
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return obj;
}

function name(a, b) {
	this.a = a;
	this.b = b;
}

let c = New(name, 1, 2)
let d = new name(1, 2)
console.log(c);
console.log(d);

对参数使用slice会阻止某些JavaScript引擎中的优化。如果你关心性能,尝试通过遍历arguments对象来构造一个新的数组。另一种方法是使用被忽视的Array构造函数作为一个函数

[].shift.call()做了什么?

获取arguments的第一个参数,改变arguments的length

slice做了什么

返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。

.slice()方法

定义和用法 slice(start, end) 方法可提取数组的某个部分,并以新的数组返回被提取的部分。

使用start(包含) 和 end(不包含)

参数来指定提取数组开始和结束的部分。

如果未指定start和end,则返回整个数组。

如果指指定一个参数,该参数作为start使用,返回包括start位置之后的全部数组。

如果是负数,则该参数规定的是从数组的尾部开始算起的位置。也就是说,-1 指数组的最后一项,-2 指倒数第二个项,以此类推。

call做了什么(具体实现会将call,apply,bind放在一起讲)

改变[]中的this指向

apply做什么(具体实现会将call,apply,bind放在一起讲)

改变this的指向

this.指向如何改变

通过 apply指向 apply将构造函数的this指向新创建的对象

将具有length属性的对象转换为数组的方法

array.form() 或者 ... 或则遍历

array.from转换时需要注意什么

类数组对象的key值为数字

slice如何实现?

Array.prototype.slice = function(start,end){
     var result = new Array();
     start = start || 0;
     end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
     for(var i = start; i < end; i++){
          result.push(this[i]);
     }
     return result;