原型链基础到进阶(个人笔记)

111 阅读6分钟

原型对象

个人笔记,方便自己看,另外感谢后盾人向军老师。本笔记是通过观看向军老师视频得出。

每个对象都有一个原型prototype对象,通过函数创建的对象也将拥有这个原型对象。原型是一个指向对象的指针。

  • 可以将原型理解为对象的父亲,对象从原型对象继承来属性
  • 原型就是对象除了是某个对象的父母外没有什么特别之处
  • 所有函数的原型默认是 Object的实例,所以可以使用toString/toValues/isPrototypeOf 等方法的原因
  • 使用原型对象为多个对象共享属性或方法
  • 如果对象本身不存在属性或方法将到原型上查找
  • 使用原型可以解决,通过构建函数创建对象时复制多个函数造成的内存占用问题
  • 原型包含 constructor 属性,指向构造函数
  • 对象包含 __proto__ 指向他的原型对象
Object.getPrototypeOf() //某个对象的原型
        let arr = {};
        let a = {};
        console.log(Object.getPrototypeOf(arr)) //Object
        console.log(Object.getPrototypeOf(a))  //Object
        console.log(Object.getPrototypeOf(a) === Object.getPrototypeOf(arr)) //true
let dog = Object.create(null,{name:{value:"dahuang"}})

创造一个原型链为null的对象

        let cat = { name: 'xiaohua' };
        let dog = Object.create(null, {
            name: {
                value: 'dahuang'
            }
        });
        console.log(Object.getPrototypeOf(cat));
        console.log(Object.getPrototypeOf(dog));//null
        console.log(dog);
在原型上添加方法
        let cat = {
            sayName() {
                console.log('xiaohua');
            }
        }
        cat.__proto__.sayDad = function () {
            console.log('proto')
        }
        cat.sayName();//xiaohua
        cat.sayDad();//proto
        cat.sayMOm();

当父级和自己有同一个方法时,很明显,自己的优先使用

使用 setPrototypeOfgetPrototypeOf 获取与设置原型
        let child = { name: 'datou' };
        let parent = {
            name: 'xiaotou',
            sayName() {
                return this.name;
            }
        };
        Object.setPrototypeOf(child, parent);
        console.dir(child)
        console.log(child.sayName());//datou
        console.log(Object.getPrototypeOf(child))
使用一个实例对象来创造一个实例对象
function User(name, age = 2) {
            this.name = name;
            this.age = age;
        }
        // User.prototype.show = function () {
        //     console.log(this.name);
        // }//因为 User.prototype重新赋值,所以已经没有show这个方法了
        User.prototype = {
            constructor: User,
            sayHi() {
                console.log('Hi,I am ' + this.name);
            }
        }
        let cat = new User('cat');
        console.dir(cat);
        function createObj(obj, ...args) {
            let constructor = Object.getPrototypeOf(cat).constructor;
            return new constructor(...args)
        }
        let dog = createObj(cat, 'dog', 1);
        console.dir(dog)
        // dog.show()
        dog.sayHi();

看一个实例吧

a instanceof A 判断A的原型是不是a原型链上的一份子

        function A() { }
        function B() { }
        function C() { }
        let c = new C();
        B.prototype = c;
        let b = new B();
        A.prototype = b;
        let a = new A();
        console.log(a instanceof C); //true
Object.setPrototypeOf(b, a)//将a设置为b原型链上的一部分
        let a = {};
        let b = {};
        Object.setPrototypeOf(b, a)//将a设置为b原型链上的一部分
        console.log(b.__proto__ === a)//true
        console.log(a.isPrototypeOf(b))//true
in 和 Object.hasOwnProperty()
        let child = { name: 'datou' };
        let father = { age: 30 }
        Object.setPrototypeOf(child, father);
        for (const key in child) {
            if (father.hasOwnProperty(key)) {
                console.log(father[key]) //30 遍历child的所有属性,因为father是child的原型,所以age属性能被访问
                // father.hasOwnProperty(key)表示只有father对象有这个key属性,if结果为真。
            }
        }
call和apply的传参问题

他们两个都是用来改变函数中的this指向问题,当需要传递参数的时候

call可以传递多个参数

apply必须以数组的形式传递多个参数

console.log(person.sayHobby.call(person1, 'swimming', 'hiking')); // I'm Coy, I like swimming and hiking.
console.log(person.sayHobby.apply(person1, ['swimming', 'hiking'])); // I'm Coy, I like swimming and hiking.

baijiahao.baidu.com/s?id=162350…(这篇文章讲的非常清楚)

利用原型借鸡生蛋
        let a = {
            data: [1, 2, 5, 2, 3, 333, 5, 56]
        }
        Object.setPrototypeOf(a, {
            max(data) {
                console.log(data)
                return data.sort((a, b) => b - a)[0];
            }
        })
        let b = {
            lesson: { js: 99, css: 98, html: 90 },
            get data() {
                return Object.values(b.lesson)
            }
        }
        console.log(a.max(a.data))
        console.log(a.max.call(null, (b.data)))
优化版借鸡生蛋,直接改变this指向,使用Math.max(), 这个函数接收多个数值
        let a = {
            data: [1, 2, 3, 4, 22, 11, 21]
        }
        let b = {
            lesson: { js: 99, css: 98, html: 90 },
            // get data() {
            //     return Object.values(b.lesson)
            // }
        }
        console.log(Math.max(...[1, 22, 12, 32, 21]))
        console.log(Math.max.apply({}, a.data))
        // Object.values(b.lesson)给的是一个数组[99,98,90], apply传值必须是数组,传过去就不是数组了
        console.log(Math.max.apply({}, Object.values(b.lesson)))
利用数组的filter方法和改变this指向来操作dom元素
<body>
    <div class="one">one</div>
    <div class="two">two</div>
    <div id="three">three</div>
    <div class="four">four</div>
    <script>
        let btns = document.querySelectorAll('div');
        btns = [].filter.call(btns, item => {
            return item.hasAttribute('class');
        })
        console.log(...btns)
    </script>
</body>
正确的定义构造函数,即减小分配内存,利用原型
  • 不恰当的定义构造函数 lisi 和zs两个对象会分别存储show() ,这个方法,造成了内存的浪费。
        function User(name) {
            this.name = name;
            this.show = function () {
                console.log(this.name);
            }
        }
        let lisi = new User('lisi');
        let zs = new User('zs');
        console.dir(lisi);
        console.dir(zs)
  • 恰当的使用原型
        function User(name) {
            this.name = name;
        }
        // User.prototype.show = function () {
        //     console.log(this.name);
        // }
        User.prototype = {
            constructor: User,
            show() {
                console.log(this.name);
            }
        }
        let lisi = new User('lisi');
        let zs = new User('zs');
        console.dir(lisi);
        console.dir(zs)
Object.create()
  • 它是一种为一个对象设置原型链 ,他创造了一个新的空间来指向目标原型,而使用对象指向这个新的空间(一个实例对象),新的空间指向目标原型(第二段代码可以解释)
        let User = {
            name: 'why',
            show() {
                console.log(this.name)
            }
        }
        // let a = Object.create(b),将b设置为a的原型,第二个参数是设置a对象的属性
        let hd = Object.create(User, {
            name: {
                value: 'wy'
            }
        })
        hd = { name: 'cat' }
        hd.show()
        function User(name) {
            this.name = name;
            this.show = function () {
                console.log(this.name);
            }
        }
        function Admin(name) {
            this.name = name;
        }
        User.prototype.sayHi = function () {
            console.log('Hi! ' + this.name);
        }
        let hd = new User('why');
        hd.sayHi()
        // User.prototype = Admin.prototype;
		// console.log(User.prototype === Admin.prototype) // true
        User.prototype = Object.create(Admin.prototype)
        console.log(User.prototype.__proto__ === Admin.prototype) //true
        let hd1 = new User('wy');
        hd1.sayHi()//报错

这个例子可以更近一步体会二者的差别

        function User(name) {
            this.name = name;
            this.show = function () {
                console.log(this.name);
            }
        }
        function Admin(name) {
            this.name = name;
        }
​
        let hd = new User('why');
        // User.prototype.__proto__ = Admin.prototype;如果将这行代码与下面一行进行替换,则sayHi方法正常使用
        User.prototype = Object.create(Admin.prototype)//报错是因为User.prototype代表的是一块新开辟的空间,
                                                       //而hd这个实例的原型还是原来的旧空间
        User.prototype.sayHi = function () {
            console.log('Hi! ' + this.name);
        }
        hd.sayHi();
hd.proto
  • 那么怎么获取对象的原型呢,通过hd.__proto__既可以设置也可以获取
        let User = {
            name: 'why',
            show() {
                console.log(this.name)
            }
        }
        hd = { name: 'cat' }
        hd.__proto__ = User;
        console.log(hd.__proto__)//{name: 'why', show: ƒ}这是一个对象
        hd.show()
setPrototypeOf,getPrototypeOf
  • setPrototypeOf设置,getPrototypeOf获取原型
        let User = {
            name: 'why',
            show() {
                console.log(this.name)
            }
        }
        hd = { name: 'cat' }
        Object.setPrototypeOf(hd, User);
        console.log(Object.getPrototypeOf(hd))//{name: 'why', show: ƒ}效果与 
        // hd.__proto__ = User; console.log(hd.__proto__)相同
        hd.show()
hd.__proto__原来是属性访问器
        function User(name) {
            this.name = name;
            this.show = function () {
                console.log('111')
            }
        }
        let hd = new User('why')
        //使用这种赋值原型的方式,是不会覆盖掉原生的原型的,如果show1变成show,hd.show()输出的仍然是111
        hd.__proto__ = {
            show1() {
                console.log('222')
            }
        }
        //再次使用这种方式赋值给一个对象,会把之前使用hd.__proto__=这种方式赋值的对象全部覆盖,无论属性方法名是否相同
        hd.__proto__ = {
            show1() {
                console.log(333)
            }
        }
        //hd.__proto__= 赋值的是一个对象才生效,数字无效,所以  hd.show2()//333  仍然可以使用
        hd.__proto__ = 99;
        hd.show()//111
        hd.show2()//333
        hd.show()//报错
  • hd.__proto__原来是属性访问器不接受非对象赋值js原理
        let hd = {
            name: {},
            get proto() {
                return this.name;
            },
            set proto(value) {
                if (value instanceof Object) {
                    this.name = value
                }
            }
        }
        hd.proto = { view: function () { } }
        hd.proto = '99';
        console.log(hd.proto) //{view: ƒ}
  • 严格意义上说,__proto__不是对象的属性,是一个getter,setter
  • 那如果我就是那设置一个__proto__这种属性名的属性怎么办呢?
  • hd. _proto__指向对象的原型,而他的原型上有一个_proto__的属性访问器,那就让hd.__proto__不指向这个本来的原型
        let a = {};
        Object.setPrototypeOf(a, null);
        // a = Object.create(null) //二者都可以改变原型指向
        a.__proto__ = 111
        console.dir(a.__proto__)
constructor
  • constructor是原型上的一个属性,他指向对象。所以我们可以通过构造函数的实例找到构造函数
        function User() {
​
        }
        let hd = new User();
        console.log(hd.__proto__.constructor === User) //true
Object.getOwnPropertyDescriptors(User.prototype) 获取对象原型的属性特征
        function User(name) {
            this.name = name;
            this.show = function () {
                console.log(this.name);
            }
        }
        function Admin(name) {
            this.name = name;
        }
​
        let hd = new User('why');
        // User.prototype.__proto__ = Admin.prototype;如果将这行代码与下面一行进行替换,则sayHi方法正常使用
        User.prototype = Object.create(Admin.prototype)//报错是因为User.prototype代表的是一块新开辟的空间,而hd这个实例的原型还是原来的旧空间
        User.prototype.sayHi = function () {
            console.log('Hi! ' + this.name);
        }
        User.prototype.constructor = User;
                // 手动将User.property这个对象的constructor属性的enumerable属性值改为false,那么constructor就不可以被遍历了
        Object.defineProperty(User.prototype, 'constructor', {
            value: User,
            enumerable: false
        })
        let a = new User;
        for (const key in a) {
            console.log(key)
        }
        console.log(Object.getOwnPropertyDescriptors(User.prototype));
        hd.sayHi();

image-20220428093716400

\