最新老婆准备找工作,找了一些js的面试题,有些不懂,其中有一个问啥是原型啥是原型链, 直接把我问懵逼了,接触js这么多年, 没有真正了解过某种用法或者技术的学名是啥。一帮所谓学者还真是取了漂亮名。 为此百度了一番,大部分博客写的一般般, 太过理论,我来点实际的

定一个类目前有两种方式:

函数式:

function A(){ this.a = 1; }
A.log = function (){}
A.prototype.log = function(){}
// 我刻意写成一样,都叫log

类式:

class A {
  constructor() {this.a = 1;}
  static log() {}
  log() {}
}

第二种方式其实是第一种方式的语法糖,只是写法不同,类底层逻辑的其实还是第一种, 但是没有人证明过, 我决定证明一下:

js hook技术的感想_类名

 两者类的定义方式我交换了顺序,看上面的图里错误信息堆栈一摸一样,编译器碰到class A, 使用compileFunction去编译的, 这一点就足以证明class写法是第一种的漂亮写法。   

还没结束,再看这个样例:

js hook技术的感想_v8_02

通过这个例子,通过class 写的类,必须要使用new。 可以证明,class A也就仅仅只是function A 定义类的方式一种漂亮写法, 并不完全等价。事实上仅仅是在实现类方面一样,但是function并不仅限类的实现

再看这个样例:

function Animal(){
    this.name = '动物';
    this.speek = function (){
        console.log('你是畜生');
    }
    return this;
}
Animal.speek = function(){
    console.log('我是畜生-通过类名调用');
}

Animal.prototype.speek = function(){
    console.log('我是畜生-通过实例调用');
}

new Animal().speek();   // 你是畜生
Animal().speek();       // 你是畜生
Animal.speek();         // 我是畜生-通过类名调用

Animal().name;          // 动物
Animal() === Animal();  // true

Animal()函数里有一个this, 有的人可能不知道this是啥,this其实表示运行的当前环境。 记住一句话,谁在调用this就代表谁。

如果通过new Animal()写的, 这个this就是实例对象,new方式底层构造函数v8调用的

如果是通过Animal()写的,this表示全局对象,因为是在文件根调用的。

如何输出内容 我是畜生-通过类名调用?

要想输出它就必须清楚这个方法存放在哪个object中?

上面的代码其实设计有4种Object

第一种Object是 global

第二种Object是 new Animal()

第三种Object是 Animal.prototype

第四种Object是 Animal

new Animal().speek 没有走原型里的方法,因为它自己定义了speek。

通过对象.方法名去调用的时候,v8有一个查找路径,优先找自己实例的方式,没有就去原型里找,原型里还有原型,一层一层往上找, 直到Object对象为止,这个是一切对象的超类。

我自己定义了speek, 还想使用原型方法, 直接上干货

  1. 删除自己的定义,然后再调用
const anim = new Animal(); 
delete anim.speek;
anim.speek();// 我是畜生-通过实例调用
  1. 直接跳过自己的定义的方法
const anim = new Animal(); 
anim.__proto__.speek.call(anim);  // 我是畜生-通过实例调用
  1. 以上两种方式,我某些app的时候都用过, 知道底层运行原理,就可以随意hook了
    比如在原来的函数后输出一句话,你才是畜生
const anim = new Animal(); 
const speek = anim.__proto__.speek;
anim.__proto__.speek = function(){
    speek.call(this);
    console.log('你才是畜生');
}
anim.__proto__.speek.call(anim);  // 我是畜生-通过实例调用\n你才是畜生
  1. 现在hook构造函数,之后不管怎么创建对象,都用原型方法一劳永逸
const oldAnimal = Animal;
Animal = function(){// hook
    delete oldAnimal.call(this).speek;
    return this;
}

Animal.prototype = oldAnimal.prototype;

const ani2 = new Animal();
ani2.speek();
  1. 超炫酷玩js
// 修改所有类的顶头上司
Object.prototype.log = function(...args){
    console.log(`${this.name}`, ...args);
}

// 任何log方法, 都走到了超类, 这就是所谓的原型链查找
new Animal().log('你才是畜生');  // 动物 你是畜生
[].log('你才是畜生');  // undefined 你才是畜生
({}).log('你才是畜生');  // undefined 你才是畜生
this.log('你才是畜生');  // undefined 你才是畜生
  1. 输出任何对象的属性和方法, 包括不可遍历属性
function getKeys(obj){
    let keys = {};
    function set(args){
        args.forEach((item) => {
            keys[item] = 1;
        });
    }

    function _(obj){
        if(!obj ){
            return;
        }

        set(Object.getOwnPropertyNames(obj))
        if(obj.__proto__){
            set(Object.getOwnPropertyNames(obj.__proto__));

            _(obj.__proto__)
        }
    }
    _(obj);
    return Object.keys(keys);
}
  1. js set get hook
  2. 闭包hook, 理论无法hook原始闭包里的局部变量,下面的代码hook,相当于重新创建了一个闭包。
    有点脱了裤子放屁的感觉,因为完全可以直接hook,而不是使用proxy
function counter() {
    var count = 0;
    return function() {
        return ++count;
    }
}

function hookCounter() {
    const handler = {
        apply: function(target, thisArg, argumentsList) {
            eval(target.toString().replace("++count;", "(count = arguments[0]), count"));
            return counter.apply(thisArg, argumentsList);
        },
    };
    
    return new Proxy(counter, handler);;
}

counter = hookCounter();
var count = counter();
console.log(count(5)); // 输出 5
console.log(count(6)); // 输出 6
console.log(count(7)); // 输出 7
console.log(counter()(10)); // 输出 10
  1. 特殊方式实现单例模式
const instanceProto = Object.create(Function.prototype)
obj.Get = function(){
    obj._ins = obj._ins || new this;
    return obj._ins;
};

const f = function(){
    console.log('f2');
    this.x = 123;
};

Object.setPrototypeOf(f, instanceProto);
const a = f.Get();
const b = f.Get();
console.log(a.x, b.x);
a.x = 1;
console.log(a.x, b.x);
  1. hook for of 循环
const obj2 = {
  dzq: "1",
  a: 2,
  c: {
    name: "dzqdzq",
  },
  [Symbol.iterator]() {
    let index = 0;
    const keys = Object.keys(this);
    const self = this;
    return {
      next() {
        if (index < keys.length) {
          return { value: self[keys[index++]], done: false };
        } else {
          return { done: true };
        }
      },
    };
  },
};

// 让数组逆序输出
Array.prototype[Symbol.iterator] = function () {
  let index = this.length;
  const data = this;
  return {
    next() {
      if (index > 0) {
        return { value: data[--index], done: false };
      } else {
        return { done: true };
      }
    },
  };
};

let arr = [12, 3, 4];
let arr2 = [122, 32, 42];

for (const item of arr2) {
  console.log(item);
}

for (const item of arr) {
  console.log(item);
}

function gen() {
  this.data = [1, 2, 3];
  this[Symbol.iterator] = function* () {
    for (const item of this.data) {
      yield item;
    }
  };
}

for (const item of new gen()){
  console.log(item);
}
  1. hook for in循环
let arr = [1, 3, 4];

const handler = {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver) * 10;
  },
};

const proxy = new Proxy(arr, handler);

for (const i in proxy) {
  console.log(proxy[i]);// 10, 30, 40
}