参数按值传递
ECMAScript中所有函数的参数都是按值传递的,就是把函数外部的值复制给函数内部的参数,就和把值从⼀个变量复制到另⼀个变量⼀样。
- 1)按值传递
var value = 1;
function foo(v) {
v = 2;
console.log(v); //2
}
foo(value);
console.log(value) // 1
当传递 value 到函数 foo 中,相当于拷⻉了⼀份 value,假设拷⻉的这份叫 _value,函数中修改的都是 _value 的值,⽽不会影响原来的 value 值。
- 2)共享传递
当值是复杂的数据结构时,就会按引用传递。函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。
var obj = {
value: 1
};
function foo(o) {
o = 2;
console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1
共享传递,在传递对象的时候,传递的是地址索引。所以修改o.value时,可以通过引用找到原值,但是直接修改o相当于把传递的对象改为传递值,所以不会改变原值。
js中数据类型分为基本类型与引⽤类型
- 基本类型值存储于栈内存中,传递的就是当前值,修改不会影响原有变量的值;
- 引⽤类型值其实也存于栈内存中,只是它的值是指向堆内存当中实际值的⼀个地址;索引引⽤传递传的值是栈内存当中的引⽤地址,当改变时,改变了堆内存当中的实际值;
call
定义
在使⽤⼀个指定的 this 值和若⼲个指定的参数值的前提下调⽤某个函数或⽅法。
let foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
- call 改变了 this 的指向,指向到 foo;
- bar 函数执⾏了;
相当于给foo函数本身添加了个bar的函数属性,执行bar,再删除这个对象属性。
1)第⼀步
// fn 是对象的属性名,反正最后也要删除它,所以起什么都可以。
foo.fn = bar
// 第⼆步
foo.fn()
// 第三步
delete foo.fn
//第一版
Function.prototype.myCall=function(context){
context.fn=this;
context.fn();
delete context.fn;
}
//测试
let foo ={value:1};
function bar(){
console.log(this.value)
};
bar.myCall(foo);//1
1)第二步
call除了可以指定this,还可以指定参数
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1
从 Arguments 对象中取值,取出第⼆个到最后⼀个参数,然后放到⼀个数组⾥
//第二版
Function.prototype.myCall=function(context){
context.fn=this;
//将传入的类数组对象arguments转换为数据,并从第二个开始取值到最后一个
let arg=[...arguments].slice(1);
//执行的时候依次传递参数
context.fn(...arg);
delete context.fn;
}
//测试
let foo ={value:1};
function bar(name,sex){
console.log(name)
console.log(age)
console.log(this.value)
};
bar.myCall(foo,'anissa','female');//1
3)第三步
当this为 null 的时候,视为指向 window
var value = 1;
function bar() {
console.log(this.value);
}
bar.call(null); // 1
可以返回值
var obj = {
value: 1
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
console.log(bar.call(obj, 'kevin', 18));
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }
//第三版
Function.prototype.myCall=function(context){
var context=context||window;
context.fn=this;
//将传入的类数组对象arguments转换为数据,并从第二个开始取值到最后一个
let arg=[...arguments].slice(1);
//返回结果
let result=context.fn(...arg);
delete context.fn;
return result;
}
//测试
var value = 2;
var obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.myCall(null); // 2
console.log(bar.myCall(obj, 'anissa', 18));
// 1
// Object {
// value: 1,
// name: 'anissa',
// age: 18
// }
apply
定义
在使⽤⼀个指定的 this 值和参数值的数组前提下调⽤某个函数或⽅法。
//第一版
Function.prototype.myApply=function(context,arr){
var context=Object(context)||window;
context.fn=this;
var result;
if(!arr){
result=context.fn();
}else{
result=context.fn(...arr);
}
delete context.fn;
return result;
}
bind
定义
bind方法会创建一个新函数。当这个新函数被调用时,bind的第一个参数将作为它运行时的this,之后的一序列参数会在传递的实参前传入作为它的参数。
- 返回⼀个函数;
- 可以传⼊参数;
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
// 返回了⼀个函数
var bindFoo = bar.bind(foo);
bindFoo(); // 1
1)第一步
关于指定 this 的指向,我们可以使⽤ call 或者 apply 实现
Function.prototype.myBind=function(context){
var self=this;
return function(){
return self.apply(context);
}
}
2)第二步
传参的模拟实现
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
当需要传 name 和 age 两个参数时,可以在 bind 的时候,只传⼀个 name,在执⾏返回的函数的时候,再传另⼀个参数 age。
//第二版
Function.prototype.myBind= function (context) {
var self = this;
// 获取bind2函数从第⼆个参数到最后⼀个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传⼊的参数
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
3)第三步
实现构造函数效果。
⼀个绑定函数也能使⽤new操作符创建对象:这种⾏为就像把原函数当成构造器。提供的 this 值被忽略,同时调⽤时的参数被提供给模拟函数。也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传⼊的参数依然⽣效。
举例
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'delyn';
var bindFoo = bar.bind(foo, 'anissa');
var obj = new bindFoo('18');
// undefined
// anissa
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// delyn
尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了,这个时候的this已经指向了obj
//第三版
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);
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来⾃绑定函数的值
// 以上⾯的是 demo 为例,如果改成 this instanceof fBound ? null : context,实例只是⼀个空对象,将 null 改成 this ,实例会具有 habit 属性
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
fBound.prototype = this.prototype;
return fBound;
}
4)第四步
构造函数效果的优化实现
我们直接将 fBound.prototype = this.prototype ,我们直接修改fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过⼀个空函数来进⾏中转。
//第四版
Function.prototype.myBind= function (context) {
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;
}
5)第五步
调⽤ bind 的不是函数时,提示错误
//第五版
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;
}
//简化版本
Function.prototype.myBind = function(context) {
// 判断是否是undefined 和 null
if (typeof context === "undefined" || context === null) {
context = window;
}
self = this;
return function(...args) {
return self.apply(context, args);
}
}
new
定义
new 运算符创建⼀个⽤户定义的对象类型的实例或具有构造函数的内置对象类型之⼀。
例子见下
function Person (name, sex) {
this.name = name;
this.sex= sex;
this.habit = 'reading';
}
Person.prototype.strength = 80;
Person.prototype.sayYourName = function () {
console.log('I am ' + this.name);
}
var person = new Person('anissa', 'female');
console.log(person.name) // anissa
console.log(person.habit) // reading
console.log(person.strength) // 60
person.sayYourName(); // I am anissa
可以看到,实例 person 可以:
- 访问到 构造函数⾥的属性;
- 访问到 构造函数prototype 中的属性;
1)第一步
因为 new 的结果是⼀个新对象,所以在模拟实现的时候,我们也要建⽴⼀个新对象,假设这个对象叫obj,因为 obj 会具有 Person 构造函数⾥的属性,我们可以使⽤ Person.apply(obj, arguments) 来给 obj 添加新的属性。
然后,实例的 proto 属性会指向构造函数的 prototype,也正是因为建⽴起这样的关系,实例可以访问原型上的属性
//第一版
Function.prototype._new= function () {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
}
- ⽤new Object() 的⽅式新建了⼀个对象 obj;
- 取出第⼀个参数,就是我们要传⼊的构造函数。此外因为 shift 会修改原数组,所以 arguments 会
被去除第⼀个参数; - 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性;
- 使⽤ apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性;
- 返回 obj;
2)第二步
假如构造函数有返回值
function Person (name, sex) {
this.strength = 60;
this.sex= sex;
return {
name: name,
habit: 'reading'
}
}
var person = new Person('anissa', 'female');
console.log(person.name) // anissa
console.log(person.habit) // reading
console.log(person.strength) // undefined
console.log(person.age) // undefined
在这个例⼦中,构造函数返回了⼀个对象,在实例 person 中只能访问返回的对象中的属性。
当只是返回⼀个基本类型的值时
function Person (name, age) {
this.strength = 60;
this.age = age;
return 'lalalalal';
}
var person = new Person ('anissa', '18');
console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18
尽管有返回值,但是相当于没有返回值进⾏处理。
也就是当返回的是对象就返回这个对象,如果不是对象,那该返回什么就返回什么。
3)第三步
// 最终版的代码
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};