参数按值传递
ECMAScript中所有函数的参数都是按值传递的。
什么是按值传递呢?
把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
var value = 1;
function foo(v) {
v = 2;
console.log(v); //2
}
foo(value);
console.log(value) // 1
很好理解,当传递 value 到函数 foo 中,相当于拷贝了一份 value,假设拷贝的这份叫 _value,函数中修改的都是 _value 的值,而不会影响原来的 value 值。
共享传递
拷贝虽然很好理解,但是当值是一个复杂的数据结构的时候,拷贝会产生性能上的问题。
这里提及一种:按引用传递。
所谓按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。
var obj = {
value: 1
};
function foo(o) {
o.value = 2;
console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
类型
基础数据类型:存放在栈内
引用类型:存放在堆内, 栈中存储了指向堆内存的指针
手写call, apply, bind
作用: 改变this指向
let foo = {
value: 1
}
function bar() {
conso;e.log(this.value)
}
//找不到value
bar()
//找得到
bar.call(foo)
//改变了this指向
//bar函数执行
call的作用逻辑
- 把函数设置为对象的属性
- 执行了函数
- 删除该函数
理解上应该是foo调用了bar
实现call
//1.
Function.prototype.call2 = function (context) {
//context 为 foo
//1. 函数设置为对象的属性
//!!this指向调用它的人
context.fn = this// foo.bar
//2. 执行
context.fn()
//3. 删除
delete context.fn
}
//2. 添加,能够加参数
bar.call(foo, 'hello')
Function.prototype.call2 = function (context) {
//使用类数组
//第一个参数是context,要去掉
var args = [...arguments].slice(1)
context.fn = this
//执行时传入
context.fn(...args)
delete context.fn
}
//3. 传入null,this指向window
Function.prototype.call2 = function (context) {
//使用或逻辑,处理传入null的环境,注意window是浏览器的环境
var context = context || window
var args = [...arguments].slice(1)
context.fn = this
context.fn(...args)
delete context.fn
}
//4. 如果函数有返回值,要将其返回
Function.prototype.call2 = function (context) {
var context = context || window
var args = [...arguments].slice(1)
context.fn = this
//拿到结果
var res = context.fn(...args)
delete context.fn
//返回
return res
}
//5. 若对象也有一个fn的对象
Function.prototype.call2 = function (context) {
var context = context || window
var args = [...arguments].slice(1)
//利用symbol解决命名冲突问题
var fnSymbol = Symbol
context[fnSymbol] = this
var res = context[fnSymbol](...args)
delete context.fn
return res
}
实现apply
与call的区别, apply接收的是数组, 因此只需要更改一下传值即可
Function.prototype.apply2 = function (context, args) {
var context = context || window
var fnSymbol = Symbol
context[fnSymbol] = this
//直接使用args
var res = context[fnSymbol](...args)
delete context.fn
return res
}
应用场景
使用Object上的toString
//这种方式判断arr的数据类型
var arr = [1, 2, 4]
Object.prototype.toString.call(arr)
在构造函数中调用父类构造函数
function Parent(name) {
this.name = name
}
function Child (name, age) {
//parent已经有name了
Parent.call(this, name)
this.age = age
}
var person = new Child('aaa', 21)
立即执行函数中 约定上下文
(function () {
console.log(this)
}).call({value: 42})
数组不更改函数的情况下调用
function multiply(a, b, c) {
return a * b * c
}
var arr = [1, 2, 3]
var res = multiply.apply(null, arr)
var res = multiply.call(null, ...arr)
实现bind
功能: 创建一个函数, 第一个参数作为运行时的this
var foo = {
value : 1,
}
function vbar(name, age) {
console.log(this.value, name, age)
}
var bindFoo = bar.bind(foo)
bindFoo('hello', 21)
//1
Function.protype.bind2 = function(context) {
//保存this指向
var self = this
return function () {
return self.apply(context)
}
}
//2. 处理参数
//var bindFoo = bar.bind(foo, 'hello')
//bindFoo(18)
Function.protype.bind2 = function(context) {
var self = this
//将context去掉
var args = Array.prototype.slice.call(arguments, 1)
return function () {
//处理剩余函数
var bindArgs = Array.prototype.slice.call(arguments)
//将剩余的参数加上
return self.apply(context, args.concat(bindArgs))
}
}
//3. 使用new操作符来调用
//var bindFoo = bar.bind(foo, 'hello')
//new bindFoo(18)
Function.protype.bind2 = function(context) {
var self = this
var args = Array.prototype.slice.call(arguments, 1)
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments)
//当作为构造函数时, this会指向fBound, 当为普通函数时, 指向context
return self.apply(
this instanceof fBound ? this : context,
args.concat(bindArgs)
)
}
//防止修改传入的函数的prototype, 空函数作为中转
var fNOP = function () {}
//指向相同的prototype
fNOP.prototype = this.prototype
//
fBound.prototype = new fNOP()
return fBound
}
手写new
//objectFactory(Person, 'D', '18')
function ObjectFactory() {
//new 一个对象
var obj = new Object()
//获取arguments的第一个参数, 因为arguments不具备数组的方法, 取出Person
Constructor = [].shift.call(arguments)
//绑定原型
obj.__proto__ = Constructer.prototype
// arguments = ['D', '18'], 将参数绑定上
//根据返回值是不是对象要进行一定处理
var res = Constructoe.apply(obj, arguments)
return typeof res === 'object' ? res : obj
}
//返回了对象
function Person (name, age) {
this.strength = 60;
this.age = age;
return {
name: name,
habit: 'Games'
}
}
var person = new Person('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
//返回基本类型
function Person (name, age) {
this.strength = 60;
this.age = age;
return 'handsome boy';
}
var person = new Otaku('Kevin', '18');
console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18
创建对象几种方法
工厂模式
function createPerson (name) {
var o = new Object()
o.name = name
o.getName = function () {
console.log(this.name)
return this.name
}
return o
}
var person = createPersoon('D')
console.log(person)
//优点:简单;
//缺点:对象无法识别,因为所有的实例都指向一个原型;
构造函数
function Person(name) {
this.name = name;
this.getName = function () {
console.log(this.name);
};
}
var person1 = new Person('kevin');
//优点:实例可以识别为一个特定的类型;
//缺点:每次创建实例时,每个方法都要被创建一次;
优化
function getName() {
}
function Person(name) {
this.name = name;
this.getName = getName();
}
//放在全局不是很合理
原型模式
利用原型
function Person(name) {
this.name = name
}
Person.prototype.name = 'xianzao';
Person.prototype.getName = function () {
console.log(this.name);
};
function Child() {
}
Child.prototype = new Parent()
var person3 = new Child()
缺点:所有的属性和方法都共享, 不能初始化参数;
优点:方法不会重新创建;
组合继承