js基础(2):类型, 手写call, bind, apply, 手写new

53 阅读4分钟

参数按值传递

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()

缺点:所有的属性和方法都共享, 不能初始化参数;

优点:方法不会重新创建;

组合继承