主要实现 call、apply、new、bind 等实现思路
call 和 apply 的模拟实现 call call()
在使用一个指定的 this 值和若干个指定的参数值的前提下,调用某个函数或方法。该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
使用 call 方法调用函数并且指定上下文的 ‘this’
1 2 3 4 5 6 7 8 9 var value = 1 ;var obj = { value: 2 }; function foo ( ) { console .log(this .value); } foo(); foo.call(obj);
使用 call 方法调用父构造函数
1 2 3 4 5 6 7 8 9 function Person (name, age ) { this .name = name; this .age = age; } function Tao (name, age, job ) { Person.call(this , name, age); this .job = job; } var tao = new Tao("yangtao" , 27 , "Teacher" );
所以我们模拟的步骤可以分为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var foo = { value: 1 , bar: function ( ) { return this .value; } }; foo.bar(); foo.fn = bar; foo.fn(); delete foo.fn;
第一版:绑定 this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Function .prototype.mycall = function (context ) { context.fn = this ; context.fn(); delete context.fn; }; var foo = { value: 1 }; function bar ( ) { console .log(this .value); } bar.mycall(foo); var foo = { value: 1 , bar: function ( ) { console .log(this .value); } }; foo.bar();
第二版:给定参数
1 2 3 4 5 6 7 8 9 10 Function .prototype.mycall = function (context, name, age ) { context.fn = this ; context.fn(); var args = []; for (var i = 1 , l = arguments .length; i < l; i++) { args.push("arguments[" + i + "]" ); } eval ("context.fn(" + args + ")" ); delete context.fn; };
第三版:传参为 null 和返回结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Function .prototype.mycall = function (context ) { var context = context || window ; context.fn = this ; var args = []; for (var i = 1 , l = arguments .length; i < l; i++) { args.push("arguments[" + i + "]" ); } var result = eval ("context.fn(" + args + ")" ); delete context.fn; return result; };
第四版:考虑 context,以及 context.fn 的可能性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Function .prototype.mycall = function (context ) { if (typeof context != "object" ) { throw new Error ("Arguments error" ); } var context = context || window ; var args = [], reslut; if ("fn" in context && context.hasOwnProperty("fn" )) { var fn = context.fn; var fnFlag = true ; } context.fn = this ; for (var i = 1 , l = arguments .length; i < l; i++) { args.push("arguments[" + i + "]" ); } result = eval ("context.fn(" + args + ")" ); if (fnFlag) { context.fn = fn; } else { delete context.fn; } return result; };
apply apply()
同call()
,只不过将多个参数值,以数组的形式传入而已。
用 apply 将数组添加到另一个数组:
1 2 3 4 var arr = ["a" , "b" ];var arr2 = [1 , 2 ];arr.push.apply(arr, arr2); console .log(arr);
使用 apply 和内置函数:
1 2 3 4 5 6 7 var nums = [1 , 10 , 3 , 6 , 2 ];var max = Math .max.apply(null , nums); var min = Math .min.apply(null , nums); var max = Math .max(...nums); var min = Math .min(...nums);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Function .prototype.myapply = function (context, arr ) { var context = context || window ; var reslut; context.fn = this ; if (!arr) { reslut = context.fn(); } else { var args = []; for (var i = 0 , l = arr.length; i < l; i++) { args.push("arr[" + i + "]" ); } eval ("context.fn(" + args + ")" ); } delete context.fn; return reslut; };
原文链接:JavaScript 深入之 call 和 apply 的模拟实现
bind 的模拟实现
bind()
方法会创建一个新函数。当这个新函数被调用,bind()第一个参数将作为它运行时的 this,之后的一系列参数将会在传递的实参前传入,作为它的参数。 — 来自于 MDN
创建绑定函数
1 2 3 4 5 6 7 8 9 10 11 var value = 1 ;var obj = { value: 2 , getValue: function ( ) { return this .value; } }; var getV = obj.getValue;getV(); var getV2 = obj.getValue.bind(obj);getV2();
配合 setTimeout
1 2 3 4 5 6 7 8 9 10 11 12 13 var value = 1 ;function Fn ( ) { this .value = 2 ; } Fn.prototype.getValue = function ( ) { setTimeout(this .msg.bind(this ), 1000 ); }; Fn.prototype.msg = function ( ) { console .log("value: " , this .value); }; var myFn = new Fn();myFn.getValue();
由此我们可以首先得出 bind 函数的两个特点:
第一版:返回函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Function .prototype.mybind = function (context ) { var self = this ; return function ( ) { return self.apply(context); }; }; var obj = { value: 1 }; function foo ( ) { return this .value; } var bindFoo = foo.mybind(obj);bindFoo();
第二版:传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Function .prototype.mybind = function (context ) { var self = this ; var args = Array .prototype.slice.call(arguments , 1 ); return function ( ) { var bindArgs = Array .prototype.slice.call(arguments ); return self.apply(context, args.concat(bindArgs)); }; }; var obj = { value: 1 }; function foo (name, age ) { console .log(this .value); console .log(name); console .log(age); } var bindFoo = foo.mybind(obj, "yang" );bindFoo(27 );
第三版:构造函数效果
bind 还有一个特点,就是:一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
也就是说,当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。
所以我们可以通过修改返回的函数的原型来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Function .prototype.mybind = function (context ) { var self = this ; var args = Array .prototype.slice.call(arguments , 1 ); var fBound = function ( ) { var bindArgs = Array .prototype.slice.call(arguments ); return self.apply( this instanceof fBound ? this : context, args.concat(bindArgs) ); }; fBound.prototype = this .prototype; return fBound; };
优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Function .prototype.mybind = function (context ) { if (typeof this !== "function" ) { throw new Error ( "Function.prototype.bind - what is trying to be bound is not callable" ); } var self = this ; var args = Array .prototype.slice.call(arguments , 1 ); var fNOP = function ( ) {}; var fBound = function ( ) { var bindArgs = Array .prototype.slice.call(arguments ); return self.apply( this instanceof fNOP ? this : context, args.concat(bindArgs) ); }; fNOP.prototype = this .prototype; fBound.prototype = new fNOP(); return fBound; };
那别忘了做个兼容:
1 2 3 Function .prototype.bind = Function .prototype.bind || function ( ) { …… };
原文链接:JavaScript 深入之 bind 的模拟实现
new 的模拟实现 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Person (name, age ) { this .name = name; this .age = age; this .habit = "Games" ; } Person.prototype.getName = function ( ) { console .log("I am " + this .name); }; var person1 = new Person("Yang" , "27" );console .log(person1.name); console .log(person1.habit); person1.getName();
由上可知,实例 person1 可以:
访问到 Person 构造函数里的属性
访问到 Person.prototype 中的属性
初步实现 分析:
因为 new 的结果是一个新对象,所以在模拟实现的时候,我们也要建立一个新对象,这个新对象会具有构造函数里的属性。
实例的 __proto__
属性会指向构造函数的 prototype
,也正是因为建立起这样的关系,实例可以访问原型上的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function objectFactory ( ) { var obj = new Object (); var Constructor = [].shift.call(arguments ); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments ); return obj; }
返回值效果实现 需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么
1 2 3 4 5 6 7 8 9 10 function objectFactory ( ) { var obj = new Object (); var Constructor = [].shift.call(arguments ); obj.__proto__ = Constructor.prototype; var ret = Constructor.apply(obj, arguments ); return typeof ret === "object" ? ret : obj; }
原文链接:JavaScript 深入之 new 的模拟实现
类数组对象与 arguments 类数组对象从读写、获取长度、遍历三个方面看,和数组貌似是一样的,但是无法直接使用数组的方法,需要借助 call 或 apply:
1 2 3 4 5 6 7 8 9 10 var likeArr = { 0 : "a" , 1 : "b" , 2 : "c" , length: 3 }; Array .prototype.slice.call(likeArr, 0 ); Array .prototype.join.call(likeArr, "&" ); Array .prototype.map.call(likeArr, item => item.toUpperCase());
类数组转数组 1 2 3 4 5 6 7 8 Array .prototype.slice.call(likeArr);Array .from(likeArr);Array .prototype.concat.apply([], likeArr);Array .prototype.splice.call(likeArr, 0 );
Arguments Arguments
对象只定义在函数体中,包括了函数的参数和其他属性。在函数体中,arguments 指代该函数的 Arguments 对象。
Arguments 对象的 length 属性,表示实参的长度。
Arguments 对象的 callee 属性,通过它可以调用函数自身。
将参数从一个函数传递到另一个函数:
1 2 3 4 5 6 7 8 9 function foo ( ) { bar.apply(this , arguments ); } function bar (a, b, c ) { console .log(a, b, c); } foo(1 , 2 , 3 );
使用 ES6 的...
运算符,我们可以轻松转成数组。
1 2 3 4 5 function func (...arguments ) { console .log(arguments ); } func(1 , 2 , 3 );
arguments 的应用其实很多,如果要总结这些场景的话,暂时能想到的包括:
原文链接:JavaScript 深入之类数组对象与 arguments