JavaScript进阶3之参数按值传递、call,apply,bind和new的实现、继承的多种方式

news2024/11/20 14:12:28

JavaScript基础

  • 参数按值传递
    • 按值传递
    • 共享传递
  • call、apply、bind和new的实现
    • this
      • 软绑定
      • 硬绑定
    • call的实现
      • 第一步
      • 第二步
      • 第三步
    • apply的实现
    • bind的实现
      • 返回函数的模拟实现
      • 传参的模拟实现
      • 构造函数效果的模拟实现
      • 构造函数效果的优化实现
    • new的实现
      • 初步实现
  • 继承的多种方式&优缺点
    • 原型链继承
    • 构造函数借用
    • 最常用的方式-组合继承
    • 原型继承
    • 最好的方式-寄生式继承

参数按值传递

按值传递

  1. ECMAScript中所有函数的参数都是按值传递的。
  2. 按值传递:把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
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

改变前:
在这里插入图片描述
在这里插入图片描述

改变后:
在这里插入图片描述
在这里插入图片描述

var obj = {
    value: 1
};
function foo(o) {
    o = 2;
    console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1

共享传递是指,在传递对象的时候,传递的是地址索引。
所以修改 o.value,可以通过引用找到原值,但是直接修改 o,并不会修改原值。所以上面两个例子其实都是按共享传递。
改变前:
在这里插入图片描述
在这里插入图片描述

改变后:
在这里插入图片描述
在这里插入图片描述

函数传递参数 ,传递的是参数的拷贝:

  1. 指针拷贝,拷贝的是地址索引;
  2. 常规类型拷贝,拷贝的是值 ;

call、apply、bind和new的实现

call,apply,bind方法解决的是:this指向

this

this首先需要跟执行上下文挂钩
绑定方式:软绑定,硬绑定(call、apply、bind)

软绑定

this.name="window"
function test() {
    console.log(this.name);
}
const zhangsan = {
    name: "zhangsan",
    test: test,
};
test()
zhangsan.test()

在这里插入图片描述

硬绑定

function test() {
    console.log(this.name);
}
const xiaoming={name:'xiaoming'}
//硬绑定
test.call(xiaoming)
test.apply(xiaoming)
const t=test.bind(xiaoming)
t()

在这里插入图片描述
注意两点:

  1. call 改变了 this 的指向,指向到 xiaoming;
  2. test 函数执行了;

call的实现

call() :在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

第一步

使用软绑定的方式实现call,

所以我们模拟的步骤可以分为:

  1. 将函数设为对象的属性;
  2. 执行该函数;
  3. 删除该函数;
// 第一步
// test 是对象的属性名,反正最后也要删除它,所以起什么都可以。
xiaoming.test = test
// 第二步
xiaoming.test()
// 第三步
delete xiaoming.test

根据上述思路,提供一版:

// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取,this指向的是 bar
    context.fn = this;
    context.fn();
    delete context.fn;  //卸磨杀驴
}

// 测试一下
 let foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call2(foo); // 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 对象中取值,取出第二个到最后一个参数,然后放到一个数组里。
上述代码的Arguments中取第二个到最后一个的参数

// 以上个例子为例,此时的arguments为:
// arguments = {
//      0: foo,
//      1: 'kevin',
//      2: 18,
//      length: 3
// }
// 因为arguments是类数组对象,所以可以用for循环
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
}

// 执行后 args为 ["arguments[1]", "arguments[2]", "arguments[3]"]

接下来使用eval拼接成一个函数

eval('context.fn(' + args +')')

考虑到目前大部分浏览器在console中限制eval的执行,也可以使用rest
此处代码为:

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    let arg = [...arguments].slice(1)  //arguments是类数组对象,将其拆分,并转为数组,然后再从第一个参数开始截取
    context.fn(...arg)  //arg数组拆分,逐步传入
    delete context.fn;
}

// 测试一下
var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1

在这里插入图片描述

第三步

  1. this 参数可以传 null,当为 null 的时候,视为指向 window
    举个例子:
var value = 1;

function bar() {
    console.log(this.value);
}

bar.call(null); // 1

在这里插入图片描述

  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.call2 = function (context) {
        var context = context || window;
    context.fn = this;

    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.call2(null); // 2

console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

这边给出最简化的写法:

Function.prototype.call2 = function(context, ...args) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  //es6 Symbol:每定义的symbol都是唯一的
  let fnSymbol = Symbol()
  //确保fn不重复,是唯一的
  context[fnSymbol] = this
  let fn = context[fnSymbol](...args)
  delete context[fnSymbol] 
  return fn
}

typeof 的安全机制,比如:
typeof fasldjfalsfjkasjfklasdjfld 不会抛出错误

补充一个无关知识点:1帧=1000/60=16.666毫秒

apply的实现

apply 的实现跟 call 类似,只是入参不一样,apply为数组

const foo={
    value:1
}
function bar(name,age){
    console.log(name,age,this.value)
}

bar.apply(foo,['ddd',20])

在这里插入图片描述

Function.prototype.apply = 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;
}

最简化版方式:

Function.prototype.apply2 = function(context, args) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let fnSymbol = Symbol()
  context[fnSymbol] = this
  let fn = context[fnSymbol](...args)
  delete context[fnSymbol] 
  return fn
}

bind的实现

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

由此我们可以首先得出 bind 函数的两个特点:

  1. 返回一个函数;
  2. 可以传入参数;

返回函数的模拟实现

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

// 返回了一个函数
var bindFoo = bar.bind(foo); 

bindFoo(); // 1

关于指定 this 的指向,我们可以使用 call 或者 apply 实现

// 第一版
Function.prototype.bind2 = function (context) {
   //context === foo
    //bar === this	
    var self = this;  
    
    // 虑到绑定函数可能是有返回值的,加上return
    return function () {
        return self.apply(context);
    }

}

传参的模拟实现

关于参数的传递

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。
这里如果不适用rest,使用arguments进行处理:

// 第二版
Function.prototype.bind2 = function (context) {

    var self = this;  //记录bar
    // 获取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));
    }
}

rest方式:

Function.prototype.bind2=function(context,...args){
    //args=['daisy']
    // bind2的入参传递的
    const self=this  //记录bar
    return function (...returnArgs){
        //returnArgs=['18']
        //18 这个函数的入参传递
        //[...args,...returnArgs] => ['daisy','18']
        return self.apply(context,[...args,...returnArgs])
    }
}

构造函数效果的模拟实现

bind 还有一个特点,就是
一个绑定函数也能使用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 = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');    //bind作为构造函数使用, this会指向实例 this->obj 否则 this->context
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了
后文中new 的模拟实现,就会知道这个时候的 this 已经指向了 obj。

// 第三版
Function.prototype.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 指向实例,此时结果为 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;
}

rest方式:

Function.prototype.bind2=function(context,...args){
    const self=this
    const fBound=function(...returnArgs){
        //this->obj  obj instanceof bindFoo === this instanceof fBound ->true 则作为构造函数使用的
        return self.apply(this instanceof fBound?this:context,[...args,...returnArgs])
    }
    // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
    //此时,修改obj的原型 -> 等同于修改返回的构造函数的原型
    fBound.prototype = this.prototype;
    // this是obj,希望最终obj身上也有bar身上的friend属性,
    // bar.prototype.friend = 'kevin'; new bindFoo生成的实例上也有这个属性,bindFoo.prototype = bar.prototype => fBound.prototype=this.prototype
    return fBound
}
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 = 'kevin';

var bindFoo = bar.bind2(foo, 'daisy');

var obj = new bindFoo('18');

console.log(obj)

在这里插入图片描述

构造函数效果的优化实现

但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:

// 第四版
Function.prototype.bind2 = function (context) {

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};   //作为中间的prototype的过度

    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 -> fNOP -> this(obj)
    // fBound的原型 -> fNOP的实例
    fBound.prototype = new fNOP();
    return fBound;
}

或:

Function.prototype.bind2=function(context,...args){
    const self=this
    
    var fNOP = function () {};   //作为中间的prototype的过度
    
    const fBound=function(...returnArgs){
        //this->obj  obj instanceof bindFoo === this instanceof fBound ->true 则作为构造函数使用的
        return self.apply(this instanceof fNOP?this:context,[...args,...returnArgs])
    }
    
    fNOP.prototype = this.prototype;  //fBound -> fNOP -> this(obj)
    // fBound的原型 -> fNOP的实例
    fBound.prototype = new fNOP();
    return fBound;
    var fNOP = function () {};   //作为中间的prototype的过度
    fNOP.prototype = this.prototype;  //fBound -> fNOP -> this(obj)
    // fBound的原型 -> fNOP的实例
    fBound.prototype = new fNOP();

在这里插入图片描述

new的实现

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一
先看看 new 实现了哪些功能。

function Person (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

Person.prototype.strength = 80;

Person.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

var person = new Person('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin

我们可以看到,实例 person 可以:

  1. 访问到 Otaku 构造函数里的属性;
  2. 访问到 Otaku.prototype 中的属性;

因为 new 是关键字,所以无法像 bind 函数一样直接覆盖,所以我们写一个函数,命名为 objectFactory,来模拟 new 的效果。用的时候是这样的:

function Person () {
    ……
}

// 使用 new
var person = new Person(……);
// 使用 objectFactory
var person = objectFactory(Person, ……)

初步实现

因为 new 的结果是一个新对象,所以在模拟实现的时候,我们也要建立一个新对象,假设这个对象叫 obj,因为 obj 会具有 Person 构造函数里的属性,我们可以使用 Person.apply(obj, arguments)来给 obj 添加新的属性。
然后,实例的 proto 属性会指向构造函数的 prototype,也正是因为建立起这样的关系,实例可以访问原型上的属性

// 第一版代码
function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);

    return obj;

};

在这一版中,我们:

  1. 用new Object() 的方式新建了一个对象 obj;
  2. 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数;
  3. 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性;
  4. 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性;
  5. 返回 obj;

测试:

function Person (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

Person.prototype.strength = 60;

Person.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};

var person = objectFactory(Person, 'Kevin', '18')

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin

假如构造函数有返回值

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

在这个例子中,构造函数返回了一个对象,在实例 person 中只能访问返回的对象中的属性。
而且还要注意一点,在这里我们是返回了一个对象,假如我们只是返回一个基本类型的值呢?
再举个例子:

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 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;

};

继承的多种方式&优缺点

原型链继承

function Parent () {
    this.names = ['xianzao', 'zaoxian'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('test');

console.log(child1.names); // ["xianzao", "zaoxian", "test"]

var child2 = new Child();

console.log(child2.names); // ["xianzao", "zaoxian", "test"]

问题:引用类型的属性被所有实例共享,数据被共享了

构造函数借用

function Parent () {
    this.names = ['xianzao', 'zaoxian'];
}

function Child () {
	//this指向谁?Child实例化出来的对象
    Parent.call(this);
}

var child1 = new Child();

child1.names.push('test');

console.log(child1.names); // ["xianzao", "zaoxian", "test"]

var child2 = new Child();

console.log(child2.names); // ["xianzao", "zaoxian"]

优点:

  1. 避免了引用类型的属性被所有实例共享;
  2. 可以在 Child 中向 Parent 传参;
function Parent (name) {
    this.name = name;
}

function Child (name) {
    Parent.call(this, name);
}

var child1 = new Child('xianzao');

console.log(child1.name); // xianzao

var child2 = new Child('zaoxian');

console.log(child2.name); // zaoxian

缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。(上述的Parent)

最常用的方式-组合继承

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {

    Parent.call(this, name); // 构造器借用了
    
    this.age = age;

}

Child.prototype = new Parent(); // 原型链继承
Child.prototype.constructor = Child; // 将构造器引用指回来

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

原型继承

function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

最好的方式-寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1513984.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营第25天|216.组和总和三、17.电话号码的字母组合

目录 一、力扣216.组合总和三1.1 题目1.2 思路1.3 代码 二、力扣17.电话号码的字母组合2.1 题目2.2 思路2.3 代码 一、力扣216.组合总和三 1.1 题目 1.2 思路 自己的想法&#xff1a;和总和问题思路类似&#xff0c;回溯法。 &#xff08;1&#xff09;k个数的组合&#xff0…

全自动守护数据安全:全自动备份文件的重要性与高效方案

一、全自动备份文件&#xff1a;数据安全的坚实防线 在数字化时代&#xff0c;电脑成为我们生活和工作中不可或缺的重要工具。然而&#xff0c;随着电脑中存储的数据量不断增长&#xff0c;数据丢失或损坏的风险也随之上升。因此&#xff0c;全自动备份文件的重要性愈发凸显&a…

【C语言】三种方式实现字符串(char*)/字符数组(char[ ])输入输出

前言 做题时经常需要用到字符串&#xff0c;写篇笔记加强记忆&#xff0c;本文用 4个例子实现字符串的输入输出操作。 scanf(); 从键盘输入数据时&#xff0c;遇到 “空格”、“回车” 都会终止。若要接受空格&#xff0c;使用 gets(); 代替 scanf(); 多个 scanf(); 同时出现…

全球首个 AI 超级工程师:拥有全栈技能,一个指令就能完成整个开发过程

全球首位AI软件工程师Devin是由初创公司Cognition推出的&#xff0c;它被认为是世界上第一个完全自主的AI软件工程师[2][15]。Devin具备强大的编程和软件开发能力&#xff0c;能够在多个方面协助或完全独立地完成软件开发任务[15]。它的核心能力包括自学新语言、开发迭代App、自…

浅谈Redis 的 保护模式(protected-mode)

今天在一台服务器上面部署了redis,发现始终无法用工具远程连接,项目里面是正常的,就是工具不行,防火墙也关闭了.折腾了一会才突然想起来,是不是触发了保护模式. 什么时候触发保护模式protected-mode: 同时满足以下两个: 1.bind未指定ip 2.未配置密码 解决方案: 编辑redis…

UL1642标准_锂聚合物电池亚马逊测试报告

UL1642标准_锂聚合物电池亚马逊测试报告 什么是锂聚合物电池UL1642标准&#xff1f; UL1642 认证要求涵盖旨在用于技术人员可更换或用户可更换应用的锂离子电池。UL1642 认证要求是为了避免锂离子电池在产品中工作时发生火灾或爆炸的风险。 锂聚合物电池 UL是Underwriters L…

2014

1,写出计算Ack(m,n)的递归算法 #include<iostream> using namespace std; int A(int m,int n){if(m0){return n1;}else if(m>0&&n0){return A(m-1,1);}else{return A(m-1,A(m,n-1));} }int main(){int m,n;cout<<"please input two number"&l…

移动端App、小程序、公众号该怎么选择,你真得知道吗?

当下移动App,小程序泛滥&#xff0c;如何选择&#xff0c;你真的知道吗&#xff1f; 今天我们就聊聊App、小程序、公众号、微应用具体是什么&#xff1f;怎么样开发&#xff1f;适合在什么情况下使用&#xff1f; 1.App、小程序、公众号、微应用之初识 App App本质来说就是手…

docker私有仓库-harbor的搭建

docker 官方提供的私有仓库 registry&#xff0c;用起来虽然简单 &#xff0c;但在管理的功能上存在不足。 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器&#xff0c;harbor使用的是官方的docker registry(v2命名是distribution)服务去完成。harbor在docker di…

23、设计模式之访问者模式(Visitor)

一、什么是访问者模式 访问者模式是一种行为型设计模式&#xff0c;它可以用于在不修改已有对象结构的情况下&#xff0c;定义新的操作方式。简单地说就是在不改变数据结构的前提下&#xff0c;通过在数据结构中加入一个新的角色——访问者&#xff0c;来达到执行不同操作的目的…

GIS软件应用(二)

任务&#xff1a; 1. 正确划分渔网并裁剪出研究区域 2. 渔网与poi数据正确空间链接并统计网格内类别POI数量 步骤&#xff1a; 将南京市边界进行投影变换&#xff0c;具体看我的这篇文章&#xff1a;GIS软件应用&#xff08;一&#xff09;-CSDN博客 选择ArcToolbox中的 Cr…

开口式霍尔电流传感器助力直流配电改造

彭姝麟 Acrelpsl 1开口式霍尔电流传感器助力直流配电改造 1.1 改造要求 系统改造要求不停电进行直流系统切改&#xff0c;即在不失去直流电源的情况下进行负荷的倒出和倒入&#xff0c;改造工程难度大。针对此需求&#xff0c;可采用开口式霍尔电流传感器来解决改造项目中直流…

【PHP+代码审计】PHP基础——流程控制

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

Mock.js 基本语法与应用笔记

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

数据分析之一些Excel函数

数据分析之Excel的使用 SUM()求和SUMIF()单条件求和SUMIFS()多条件求和日期函数YEAR()提取年份MONTH()提取月份DAY()提取日DATE()函数 SUBTOTAL()求和IF()函数IF嵌套 VLOOKUP()搜索取值MATCH()返回行值或列值INDEX()定位取值 SUM()求和 SUM(number1,[number2],…) 对选中的区域…

CSS学习2

自己在工作中总是有一些自动化开发的需求&#xff0c;总是以为自己是有前端基础的&#xff0c;但是一写页面&#xff0c;布局都布不好&#xff0c;真是搞笑&#xff0c;说起来还是基本功不扎实啊&#xff0c;这里在重新复习一下&#xff0c;然后记录一下文档。后边在写两个综合…

Linux 配置ssh、scp、sftp免密登录

SSH&#xff08;Secure Shell&#xff09;是一种安全的远程登录协议&#xff0c;它使用客户端-服务器架构促进2个系统之间的安全通信&#xff0c;并允许用户远程登录服务器。在某些高可用环境下&#xff0c;服务器之间可能还需要配置免密互信&#xff0c;即基于密钥验证登录。 …

linux系统使用head和tail命令,快速切分json 格式的数据集

文章目录 介绍切分训练集切分测试集 介绍 json格式的数据集&#xff0c;每一行都是一个单独数据单元。 data.json的文件格式如下&#xff1a; {"text": "彭小军认为&#xff0c;国内银行现在走的是台湾的发卡模式&#xff0c;先通过跑马圈地再在圈的地里面选择…

产品实操——设计阶段

一、思维导图&#xff1a; 二、原型图&#xff1a; 1.墨刀&#xff1a;

【代码随想录 | 数组 05】螺旋矩阵 ||

文章目录 5.螺旋矩阵25.1题目5.2思路 5.螺旋矩阵2 5.1题目 59. 螺旋矩阵 II 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例一&#xff1a; 输入&#xff1a;n 3 输出&#xff…