使用new关键字生成一个对象内部的实现过程
var arr = new Array()
-
创建新对象,即var arr = {};
-
将创建的新对象arr的_proto_属性指向构造函数的prototype对象;即arr._proto = Array.prototype,将构造函数的作用域赋给新对象(this指向新对象);
-
创建的新对象arr调用构造函数,即Array.call(arr);
-
返回新对象,如果构造函数返回的是基本数据类型,则返回的arr是构造函数的对象;如果构造函数返回的不是基本数据类型值(funcition,对象等),则返回的arr是该构造函数return的值 (function,对象等)
function Person(name,age){
this.name = name;
this.age = age;
this.myFn = function(){
console.log(this.name + this.age)
}; }function newFn(){
var constructorFun = Array.prototype.shift.call(arguments);//获取构造函数Person
var instance = Object.create(constructorFun.prototype);
constructorFun.apply(instance, arguments);
return instance; }var person = newFn(Person, 'liming','30'); person.myFn();
// arguments 是一个类数组对象,虽然有下标,但是并非真正的数组;直接调用shift会报错 // Array.prototype.shift.call(arguments) call是用来改变函数执行时this的指向,这里以arguments对象为this来执行Array.prototype.shift函数,删除数组的第一个元素并返回
call、apply、bind的区别
call&apply
每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同
function sum(num1,num2) {
return num1 + num2;
}
function callSum1(num1,num2) {
return sum.apply(this,arguments);
}
function callSum2(num1,num2) {
return sum.apply(this,[num1,num2]);
}
function callSum3(num1,num2) {
return sum.call(this,num1,num2);
}
alert(callSum1(10,10));//20
alert(callSum2(10,10)); //20
alert(callSum3(10,10)); //20
//callSum1在执行sum函数时传入了this作为sum函数的this值 以及 arguments对象
//callSum1是在全局作用域下被调用,因此this是window对象
使用call、apply扩充函数运行的作用域
var str = '周日'
function fn(){
console.log(this.str)
}
var obj = { str: '星期日'}
fn();//'周日'
fn.apply(window);//'周日'
fn.apply(this);//'周日'
fn.apply(obj);//'星期日'
//使用call和apply来扩充作用域最大的好处,就是对象不需要与方法有任何耦合关系
bind
bind()这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值
var str = '周日'
function fn(){
console.log(this.str)
}
var obj = { str: '星期日'}
var fnBind = fn.bind(obj);
fnBind();//'星期日'
console.log(fnBind._proto_ === obj.prototype) //true
//fn()调用bind()并传入对象obj,创建fnBind()函数
//fnBind函数的this是obj,因此即使在全局作用域中调用,也并不影响
总结:
(1)call、apply只是切换了函数内部this的调用,但是执行的方法依然是原始对象上的方法;即使你在 Array.prototype.slice.call(obj)的 obj 上 覆盖了slice 方法 ,依然会执行 Array 上的 slice 方法;
(2)由于call、apply方法改变的是 函数执行时所在的对象,会立即执行函数,因此需要把绑定语句写在函数体内;使用函数改变this指向时最好使用bind方法;bind是返回改变了上下文后的一个函数;
(3)bind方法每运行一次就会返回一个新函数,这会引发一些新问题。
element.addEventListener('click', o.m.bind(o));
click绑定bind方法生成了一个匿名函数,这样会导致无法取消绑定;
element.removeEventListener('click', o.m.bind(o));//这个就无效了
正确的方式是:
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
call&apply的实现原理
fn.apply(obj, array)实现原理
-
将要执行的函数fn设置为对象Obj的属性
-
执行obj.fn(args)函数
-
不能给obj对象添加新的属性,因此要删除这个属性
-
其中要对obj进行判断,为null和undefined时,要把this执行window
Function.prototype.call = function (context) {
// 首先判断是不是函数调用该方法,如果不是,则抛出异常
if (typeof this !== "function") throw new TypeError('Error'); // 为null或undefined时,要把this指向window
var context = context || window;
// 将函数设置为obj的属性
context.fn = this;
// 类数组解构参数
var args = [...arguments].slice(1);
// 执行函数
var result = context.fn(...args);
// 删除函数
delete context.fn;
// 返回执行结果
return result; } var a22 = 55; function f22(){
console.log(this.a22);//55 } f22.call();//context.fn = this;因为f22.proto = Function.prototype,f22是通过Function实例化生成的对象 实例化对象 执行 构造函数的原型方法,则方法里的this指向实例化对象即f22
//在window对象上添加了fn属性,fn属性值为f22;相当于window.fn = f22() //执行window.fn(),返回执行结果,删除window上的fn属性
bind简单实现原理
Function.prototype.myBind = function(context){
if(type this !== "function") throw new TypeError('error');
var me = this;
var args = [...arguments].slice(1);
return function (){
return me.apply(context, args.concat(...arguments);
// return me.apply(context, args.concat(Array.prototype.slice(args)))
}
}
类数组对象
数组是一个特殊的object对象。js中存在一种类数组对象。比如{0:1,length:1}或者Dom对象,或者arguments对象;数组只是一种特殊的对象,数组的特殊性体现在,它的键默认是按次序排列的整数(0,1,2...),所以数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。
var obj = {0:1,1:1,2:2,length:3} //length属性很重要,有了length就可以像数组一样遍历这个对象
Array.prototype.slice.apply({0:1,length:1}); 通过apply,将slice方法中的this指向对象{0:1,length:1},遍历生成新的数组对象
常常碰到需要将arguments对象转成Array来处理的情形,就可以用这个技巧
var arg0 = Array.prototype.slice.apply(arguments);
//apply是用来改变函数执行是this指向的,这里以argumens对象为this来执行Array.prototype.slice函数,而Array.prototype.slice函数不带参数时默认返回的是数组对象本身。
当然也可以通过类数组解构的方式,转化成数组
var arr = [...arguments]
参考链接: