小别致真东西
文章77
标签31
分类26
如何手动实现一个New操作

如何手动实现一个New操作

写在前面


在所有的前端面试中常常喜欢考面试者如何手写一个new操作符,作为在准备秋招的大三党,我也要考虑这些。
那么我们先看看new操作符都干了什么事情,有哪些操作?通过下面的代码来进行思考:

// 新建一个类(构造函数)
function Otaku(name, age) {
this.name = name;
this.age = age;
// 自身的属性
this.habit = 'pk';
}
// 给类的原型上添加属性和方法
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () {
console.log('I am ' + this.name);
}
// 实例化一个person对象
const person = new Otaku('乔峰',5000);
person.sayYourName();
console.log(person);//打印出构造出来的实例
```
![控制台打印结果](http://p9utic4op.bkt.clouddn.com/new.png)


## 解析

从控制台打印出来的结果我们可以看出new操作符大概做了一下几件事情:

1. 返回(产生)了一个新的对象
2. 访问到了类Otaku构造函数里的属性
3. 访问到Otaku原型上的属性和方法 并且设置了this的指向(指向新生成的实例对象)

通过上面的分析展示,可以知道new团伙里面一定有Object的参与,不然对象的产生就有点说不清了。 先来边写写:

```js
// 需要返回一个对象 借助函数来实现new操作
// 传入需要的参数: 类 + 属性
const person = new Otaku('乔峰',5000);
const person1 = objectFactory(Otaku, '鸠摩智', 5000);

// 开始来实现objectFactory 方法
function objectFactory(obj, name, age) {}
// 这种方法将自身写死了 如此他只能构造以obj为原型,并且只有name 和 age 属性的 obj
// 在js中 函数因为arguments 使得函数参数的写法异常灵活,在函数内部可以通过arguments来获得函数的参数
function objectFactory() {
console.log(arguements); //{ '0': [Function: Otaku], '1': '鸠摩智', '2': 5000 }
// 通过arguments类数组打印出的结果,我们可以看到其中包含了构造函数以及我们调用objectfactory时传入的其他参数
// 接下来就是要想如何得到其中这个构造函数和其他的参数
// 由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:
// 1. Array.from(arguments).shift(); //转换成数组 使用数组的方法shift将第一项弹出
// 2.[].shift().call(arguments); // 通过call() 让arguments能够借用shift方法
const Constructor = [].shift.call(arguments);
const args = arguments;
// 新建一个空对象 纯洁无邪
let obj = new Object();
// 接下来的想法 给obj这个新生对象的原型指向它的构造函数的原型
// 给构造函数传入属性,注意:构造函数的this属性
// 参数传进Constructor对obj的属性赋值,this要指向obj对象
// 在Coustructor内部手动指定函数执行时的this 使用call、apply实现
Constructor.call(obj,...args);
return obj;
}

```

- 上面的代码注释太多,剔除注释以后的代码:

```js
function objectFactory() {
let Constructor = [].shift.call(arguments);
const obj = new Object();
obj.__proto__ = Conctructor.prototype;
Constructor.call(obj,...arguments);
return obj;
}
```
- 还有另外一种操作:

```js
function myNew(Obj,...args){
var obj = Object.create(Obj.prototype);//使用指定的原型对象及其属性去创建一个新的对象
Obj.apply(obj,args); // 绑定 this 到obj, 设置 obj 的属性
return obj; // 返回实例
}
闭包

闭包

闭包是由该函数和其执行上下文共同构成,能够读取其他函数那边变量的函数。
可以用来做数据缓存、对象的私用方法等

数组

数组

数组

定义:

一个存储元素的线性集合, 元素可以通过索引(通常为数字)来任意存取。

数字索引在内部被转换为字符串类型、这是因为在javaScript中对象的属性名必须是字符串。而数组只是一种特殊的对象

创建数组

  • 通过构造函数
    <!-- 传入一组元素进行数组初始化 -->
    var arr = New Array(1, 2, 3, 4, 5);
    print(arr.length); // 5
    <!-- 只传一个元素,声明数组的初始化长度, 其中每个元素初始化为 undefined -->
    var arr1 = new Array(10);
    print(arr1.length); // 10
JS 基本数据类型和引用类型

JS 基本数据类型和引用类型

Js 基本数据类型

js基本数据类型包括:undefined, null, number, boolean, string, symbol, bigInt(新增)。基本数据类型是按值访问的,就是说我们可以操作保存在变量中的实际的值

1.基本数据类型的值是不可改变的

任何方法都无法改变一个基本类型的值是不可改变的,比如一个字符串:

var name = "change";
name.substr();//hang
console.log(name);//change

var s = "hello";
s.toUpperCase()//HELLO;
console.log(s)//hello

通过这两个例子, 我们原来发现定义的变量 name 的值始终没有发生改变,而调用 substr() 和 toUpperCase() 方法后返回的是一个新的字符串,跟原先定义的变量 name 并没有关系

按值访问

按值进行访问,操作的是保存在变量中实际的值

不可添加方法属性

基础类型的比较是值的比较

基础类型存放在栈区 变量标识符 + 变量值

引用类型

同时保存在栈区和堆区中

栈区保存变量标识符和指向堆区的方法

基本包装类型(包装对象)

this

this

this-用于访问当前方法所属的对象

const Obj = {
name: 'jack',
fn() {
console.log(this == Obj);
}
}

Obj.fn(); // true
function showThis() {
console.log(this);
}

show(); // window 在此时 show 相当于被 window 对象调用 本身属于 window 是 js 一开始 this 设计的错误 作者可能当时没有考虑清楚

// 在严格模式下 函数直接调用时 this 指向 undefined
'use strict';
function showThis() {
console.log(this);
}

showThis(); // undefined

setTimeout(showThis, 100); // window 此时的 this 指向 window 因为 setTimeout 属于 window 对象
// 相当于
window.setTimeout(showThis, 100);

每个新生成的函数内部都会新建一个 this、这个 this 在函数被调用的时候被绑定
this 在运行时进行绑定
this 提供了一种更为优雅的方式隐式传递一个对象的引用,让 API 设计更加简洁且易于复用

应用场景:

  • 普通函数中的 this 指向全局
  • 构造器里的 this 指向 new 返回的新对象
  • 函数作为方法被调用时,this 指向该对象
  • 箭头函数不会创建自己的 this,使用一个封闭上下文中的 this

改变 this 的指向:

  • .apply()
  • .bind()
  • .call()

forEach 中的 this 指向

const myForEach(cb, thisArg) {
for(let i = 0; i < this.length; i ++) {
cb.call(thisArg, (this[i]));
}
}

// 使用 forEach
const arr = [1, 2, 3];
arr.forEach(function(item){
console.log(this, item); // undefined 1 undefined 2 undefined 3 此时的 this 指向为 undefined
})

改变 this 指向 call 里面的参数

RegExp 正则表达式

RegExp 正则表达式

RegRxp(正则表达式)

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等,简化对字符串的操作

排序

排序

算法题

如下数字: 35 99 18 76 12  
  • 桶排序
    有[100] for初始化数组
    将数字放到相应的位置

    思想

        利用数组的下标是有序的,待排序的数字大小在下标的范围内,当数组下标等于要排序的数组时
    用待排序的数字坐桶的下标 给相应项+1 类似于做标记的
    myIdea:当下标等于待排序的数字大小 即输出下标 相当于输出较小的数字
    ```

    **时间复杂度:**
    时间复杂度 循环
    O(M+N) 一重循环最大值99 100,又一重循环N,
    之后又有一层循环M 有值的桶子
    嵌套循环 >= 0 1 2 3有限的,<N M+N
    多层循环是最花时间M+N
    O(M+N+M+N)=O(2*(M+N)) O(M+N)

    **缺点:** 占物理内存,因为要分配M个元素的数组

    - 冒泡排序

    ```js
    // 冒泡排序
    // 思想: 比较相邻的元素。如果第一个比第二个大,就交换两数顺序
    const source_arr = [35,18, 99, 18, 76, 12];
    function bubbleSort(arr) {
    const len = arr.length;
    for (let i = 0; i < len; i++) {
    for (let j = 0; j < len - 1 - i; j++) {
    if (arr[j] > arr[j+1]) { // 相邻元素两两对比
    let temp = arr[j+1]; // 元素交换
    arr[j+1] = arr[j];
    arr[j] = temp;
    }
    }
    }
    return arr;
    }
    console.log(bubbleSort(source_arr));
  • 选择排序

    第一重循环默认把 i 个作为最小值与后面 j 循环中的的数进行比较,也就是通过第二重循环与后面的值进行比较

    // n^2
    // 找到数组中最小的值放在第一位,第二小的放在第二位...
    // 基址查询
    function SelectSort(arr) {
    let len = arr.length
    for(let i = 0; i < len; i++) {
    let minIndex = i
    // 如果已经选择了, 前面的就一定是有序的
    for( let j = i; j < len; j++) {
    if( arr[j] < arr[minIndex]) {
    // 循环和默认的最小值进行比较, 如果更小纪录下标
    minIndex = j
    console.log(i, j)
    }
    }
    if(minIndex !== i) {
    [arr[minIndex], arr[i]] = [arr[i], arr[minIndex]]
    }
    }
    return arr
    }
    console.log(SelectSort([45,21,45,12,56,12,67,32,98]))

  • 快排排序

    因为采用了分治思想,所以快 形象的例子 三个数之间进行排序 a b c 再加递归

// 简单排序: 冒泡、选择、插入 时间复杂度都是 n^2
// 复杂排序: 快排、堆排、归并排序 时间复杂度都是 (log2^n)* n
// 分治
// 快排 log2(n)* n for循环 n次
// 1. 随机选择一个数组中的一个数作为一个基准 一般是中点
// 2. 其余数字跟他比较, 小的放左边 大的放右边
// 3. 利用递归的思想, 将左右两边的数重复以上两步
function QuickSort(arr) {
// 这里是出口 当数组长度小于等于1的时候结束递归
if(arr.length <= 1) {
return arr
}
let pivotIndex = Math.floor(arr.length /2)
// 把中间基准值从数组提出来
pivot = arr.splice(pivotIndex, 1)[0] // 选出相应位置的值
console.log(pivot)
let left = [], right = []
for(let i = 0, len = arr.length ; i < len; i++) {
if( arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return QuickSort(left).concat(pivot, QuickSort(right))
}
console.log(QuickSort([85, 24, 63, 45, 17, 31, 78, 56]))
JS对数组去重的几种方法

JS对数组去重的几种方法

面试经常问的一道题
JS对数组去重的几种方法 (前面六种方法是普通数组,最后一种是对象数组)