原型和原型链

JavaScript是一个基于原型继承的,ES6的class其实也是原型继承

  • 对象具有__proto__属性,指向原型对象

  • 函数具有prototype属性,指向原型对象; 同时函数作为对象,也有__proto__属性

  • 原型对象具有constructor属性指向构造函数

  • 原型对象上的属性和方法,将被实例对象共享

  • 构造函数内的属性和方法,在实例化新对象的时候各自创建(占内存)

    Person.prototype.__proto__ == Object.prototype    // true
    Object.prototype.__proto__ == null     // true
    
    function User(){}
    User.prototype.constructor == User
    

原型

原型链

getPrototypeOf

  • 早期Object.create()能够指定一个对象的原型,生成一个新的实例,但是我们通过对象获取其原型对象
  • 浏览器厂商在实例上添加__proto__来实现对象访问原型的功能
  • 后来getPrototypeOfsetPrototypeOf API让我们实现获取和设置原型
const User = function(name) {
    this.name = name
}

// 直接重写构造函数的prototype,记住要制定constructor属性
User.prototype ={
    constructor: User,
    show() {
        console.log(this.name)
    }
}

let x = new User('x man');    // x man
console.log(x)

function createByObject(obj, ...args){
    //获取对象的原型,得到其对应的构造函数
    const constructor = Object.getPrototypeOf(obj).constructor;
    //通过构造函数生成与obj对象相同原型的实例
    return new constructor(...args)
}

let y = createByObject(x, "y man");
console.log(y)      // y man

原型方法借用

  • function.call(this, Arg, arg1, arg2, ...)
  • func.apply(thisArg, [argsArray])
  • call、bind、apply都是Function.prototype上方法
<body>
    <div>
        <button>btn1</button>
        <button>btn2</button>
        <button class="red">btn3</button>
    </div>
    <script>
        let btns = document.querySelectorAll("button");
        btns = Array.prototype.filter.call(btns, item=> item.hasAttribute('class'))
        // btns =[].filter.call(btns, item=> item.hasAttribute('class'))
        // btns =[].filter.apply(btns, [item=> item.hasAttribute('class')])
        console.log(btns)              // [button.red]
    </script>
</body>
  • filter方法内部需要用到this,我们需要通过call或者apply将context传入
  • Math.max这种可以直接使用Math.max(1,2,3,4,2),或者Math.max.call(null,1,2,3,4)
<!--  不要这样干!!  -->
<body>
	<button onclick="this.hide()">
        Hide this btn
    </button>
</body>

<script>
	Object.prototype.hide = function() {
		this.style.display = "none"
    }
</script>

__proto__

  • 这是一个浏览器厂商实现的非标准属性

  • 该属性只能被显示设置为对象,因为Object.prototype上是getter和setter,setter限制了这个操作

  • 如果强制要把其设置为基本类型:

    const obj = Object.create(null)
    // 此时obj.__proto__为undefined
    obj.__proto__ = 1
    

constructor

function User () {}
User.prototype.show = function(){ console.log("User show") }

function Admin () {}
Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = "Admin"

User.prototype.constructor == User   // true
Admin.prototype.constructor == User  // true
  • Admin构造函数的原型指向User.prototype, Admin继承了User

  • AdminUser.prototype作为原型,Admin自身没有constructor属性,但是Admin.prototype能够拿到User.prototype的方法和属性,就包括了constructor

  • 直接改变原型对象的继承会丢失constructor ,需要补充上这个属性,并且这是一个不可枚举的属性,否则将会出现在for in遍历中

    for(let key in new Admin()) {
        console.log(key)  // role constructor show
    }
    
    Object.defineProperty(Admin.prototype,"constructor", {
        value: Admin,
        enumerable: false
    })
    
    for(let key in new Admin()) {
        console.log(key)  // role show
    }
    

ES5的继承

继承是原型的继承,只要保留原来构造函数的原型,将构造函数的原型指向Target.prototype即可

  1. 构造函数的原型是个对象Teacher.prototype,这个对象的原型就是Object.prototype。当我们显示修改这个便可以实现原型的继承

    Teacher.prototype.__proto__ == Object.prototype  // true
    
    // Teacher继承Person,获得Person上的属性和方法
    Teacher.prototype.__proto__ = Person.prototype
    
  2. ✅ 通过Object.create新建具有指定原型的空对象,需要指定constructor(最好define enumerable为false)

    这时候Teacher的原型方法需要再定义,因为原来的原型已经被替换了

    Teacher.prototype = Object.create(Person.prototype)
    Teacher.prototype.constructor = Teacher
    Teacher.prototype.show = function () { 
    	// ...
    }
    

in和instanceof

  • in可以判断属性是否在对象或者其原型

    let a= {name: 'hello'};
    Object.prototype.age = 24;
    
    console.log('name' in a) // true
    console.log('age' in a)  // true
    
  • Object.hasOwnProperty指示对象自身属性中是否具有指定的属性

let a= {name: 'hello'};
Object.prototype.age = 24;

for(const key in a) {
    console.log("in : " + key);
    if(Object.hasOwnProperty(key)){
        console.log("own : " + key)
    }
}

// in : name
// own : name
// in : age

Tab构造函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100vw;
      height: 50vh;
    }

    main {
      width: 400px;
      flex-direction: column;
      position: relative;
      margin-right: 20px;
    }

    main nav {
      display: flex;
      height: 50px;
      align-items: center;
    }

    main nav a {
      background: #95a5a6;
      margin-right: px;
      padding: 10px 20px;
      border: solid 1px #333;
      color: #fff;
      text-decoration: none;
    }

    main nav a:first-of-type {
      background: #e67e22;
    }

    section {
      height: 200px;
      width: 100%;
      background: #f1c40f;
      position: absolute;
      font-size: 5em;
      display: none;
    }

    .hd-tab section:first-of-type {
      display: block;
    }

    section:nth-child(even) {
      background: #27ae60;
    }
  </style>
</head>


<body>
  <main class="tab1">
    <nav>
      <a href="javascript:;">Tab 1</a>
      <a href="javascript:;">Tab 2</a>
    </nav>
    <section>1</section>
    <section>2</section>
  </main>
  <main class="tab2">
    <nav>
      <a href="javascript:;">TAB ONE</a>
      <a href="javascript:;">TAB TWO</a>
    </nav>
    <section>1</section>
    <section>2</section>
  </main>
</body>

<script>
  //继承工厂
  function extend(sub, sup) {
    sub.prototype = Object.create(sup.prototype);
    sub.prototype.constructor = sub;
  }

  //动作类
  function Animation() { }
  Animation.prototype.show = function () {
    this.style.display = "block";
  };
  //隐藏所有元素
  Animation.prototype.hide = function () {
    this.style.display = "none";
  };
  //必变元素集合背景
  Animation.prototype.background = function (color) {
    this.style.background = color;
  };

  //选项卡类
  function Tab(tab) {
    this.tab = tab;
    this.links = null;
    this.sections = null;
  }
  extend(Tab, Animation);
  Tab.prototype.run = function () {
    this.links = this.tab.querySelectorAll("a");
    this.sections = this.tab.querySelectorAll("section");
    this.bindEvent();
    this.action(0);
  };
  //绑定事件
  Tab.prototype.bindEvent = function () {
    this.links.forEach((el, i) => {
      el.addEventListener("click", () => {
        this.reset();
        this.action(i);
      });
    });
  };
  //点击后触发动作
  Tab.prototype.action = function (i) {
    this.background.call(this.links[i], "#e67e22");
    this.show.call(this.sections[i]);
  };
  //重置link与section
  Tab.prototype.reset = function () {
    this.links.forEach((el, i) => {
      this.background.call(el, "#95a5a6");
      this.hide.call(this.sections[i]);
    });
  };

  new Tab(document.querySelector(".tab1")).run();
  new Tab(document.querySelector(".tab2")).run();
</script>

</html>
上次更新:
贡献者: Ahon-pan