• JavaScript – Object, Prototype, Function, Class


    介绍

    JavaScript 有 4 个概念一定要搞清楚.

    Object, Prototype, Function, Class

    简单说一下:

    Object

    C# 很少直接用 object, 通常都是会开一个 class, new class 出 object

    但是 JavaScript 却不同, JavaScript 更多的是直接开一个 object, 比较少开 class. 而且 JavaScript 的 Object 有很多好玩的特性.

    Prototype

    原型链是很好的一种设计, 但坑也不少. JavaScript class 的继承就是靠 prototype 完成的, 当然 prototype 其实还可以做其它的事情.

    Function

    JavaScript 的 function 除了是 function, 还带有 class 的特性. 比如它可以 new

    Class

    JavaScript 的 class 是假的, 语法糖来的. 它外部和 C# 的 class 特性一样, 但背地里它是用 Function Prototype Object 的特性实现的.

    Object

    create object

    const obj = {
      name: 'Derrick',
      age: 11,
    };

    get / set property value

    console.log(obj.name); // get property value
    obj.age = 13;          // set property value

    new / delete property

    对象的属性是可以动态添加和删除的

    obj.lastName = 'Yam';  // new property
    delete obj.lastName;   // delete property

    getter in object

    甚至可以直接定义 getter 属性

    const obj = {
      firstName: 'Derrick',
      lastName: 'Yam',
      get fullName() {
        return `${this.firstName} ${this.lastName}`;
      },
    };

    Object.defineProperty

    对象的 property 还可以 set 一些 config 的. 通过 defineProperty 来配置.

    Object.defineProperty(obj, 'age', {
      writable: true,
      enumerable: true,
      configurable: true,
      value: 11,
      get() {
        return 11;
      },
      set(value) {},
    });

    writable 表示属性是否可以 set value. 如果设置成 false, 当 assign value to property 时 obj.age = 15,  age 的值不会有任何改变 (它虽然不会报错, 但操作被无视了)

    enumerable 表示属性是否能被遍历 (下面会讲到如何遍历属性), false 就是说不能被遍历

    configurable 表示是否可以被 defineProperty. 一旦设置成 false 就不能改回来了. lock 死掉了.

    get 就是 getter 方法

    set 就是 setter 方法. e.g. obj.age = 15 的时候触发, value 就是 15

    value 就是属性值咯

    注1: writable, enumerable, configurable 默认值都是 false, value 是 undefined

    注2: cannot both specify accessors and a value or writable attribute (有 getter setter 就不可以有 value 和 writable)

    getter setter

    完整的 getter setter 长这样

    Object.defineProperty(obj, '_age', {
      writable: true,
      enumerable: false, // 不允许被遍历
      configurable: true,
      value: 0,
    });
    
    Object.defineProperty(obj, 'age', {
      enumerable: true,
      configurable: true,
      get() {
        console.log('拦截 getter');
        return this._age;
      },
      set(value) {
        console.log('拦截 setter');
        this._age = value;
      },
    });
    obj.age = 15;
    console.log(obj.age);

    _age 是 "private 属性"

    遍历 object

    Object.keys(obj); // ['firstName', 'lastName', 'fullName']
    Object.entries(obj); // [['firstName', 'Derrick'], ['lastName', 'Yam'], ['fullName', 'Derrick Yam']]
    for (const [key, value] of Object.entries(obj)) {}

    keys 可以获取所有属性名

    entries 可以获取属性和值 (es2017)

    values 可以获取所以属性值

    注1: 属性必须是 enumerable: true 才能被遍历

    注2: 属性是 Symbol 的话, 是无法被遍历的

    注3: object 不是 iterator 所以不能直接使用 for...of obj, 必须用 Object.keys 或者 Object.entries

    如果想获取到 enumerable: false 的属性或者 Symbol 属性, 需要使用下面这 2 个方法.

    Object.getOwnPropertyNames(obj);
    Object.getOwnPropertySymbols(obj);

     

    Prototype

    用 Chrome DevTools 打开 Object 会发现它有一个特别的属性叫 Prototype

    原型链的查找过程

    prototype 有啥用呢?

    prototype 也是一个对象, 可以这么理解, 一个对象里头, 又连着另一个对象.

    举例: 对象 child 连着对象 parent

    当访问属性值时, e.g. child.prop, JavaScript 会先去找 child 对象中是否有 prop 这个属性. 有的话就返回它的值.

    如果没有这个属性, 那么它会接着去 prototype parent 寻找这个属性. 有的话就返回它的值.

    如果还是没有那就再去 parent 的 prototype 找, 一直找直到某个 prototype = null 才结束.

    指定 Prototype

    const obj = {};
    const objPrototype = Object.getPrototypeOf(obj);
    console.log(objPrototype === Object.prototype); // true
    console.log(Object.getPrototypeOf(Object.prototype)); // null

    当我们创建一个对象时, 它默认就有连着一个 prototype. 那就是 Object.prototype

    通过 Object.getPrototypeOf 可以获取到对象的 prototype.

    Object.prototype 定义了一些 hasOwnProperty 之类的方法. 所以虽然 obj = {} 看上去是空的, 但却可以调用 obj.hasOwnProperty 方法. 就是因为原型链查找的缘故.

    通过 Object.create 可以在创建对象时指定其 prototype

    const myPrototype = {
      age: 11,
    };
    const obj = Object.create(myPrototype);
    console.log(obj.age); // 11

    原型链: obj > myPrototype > Object.prototype

    动态替换 prototype

    Object.setPrototypeOf(obj, myPrototype);

     遍历 prototype 属性

    上面介绍的 Object.keys, Object.entries 都只能遍历当前对象的属性. 

    for...in 除了可以遍历对象属性还可以遍历出所有 prototype 链上的属性 (属性必须是 enumerable: true)

    for (const key in obj) {
      const isFromPrototype = !obj.hasOwnProperty(key); // 判断是当前对象还是 prototype 属性
    }

    小心坑

    看注释

    const parent = {
      age: 11,
      fullName: {
        firstName: '',
        lastName: '',
      },
    };
    const child = Object.create(parent);
    console.log(child.age); // read from parent
    child.age = 15;         // 注意: 这里不是 set property to parent 而是 new property to child
    console.log(child.age); // 注意: read from child
    
    console.log(child.fullName.firstName); // read from parent
    child.fullName.firstName = 'Derrick';  // 注意: 这里是 set property to parent, 而不是 new property to child 哦 (和上面不同)
    console.log(child.fullName.firstName); // 注意: still read from parent

    以前 AngularJS 的 $scope 就使用了 prototype 概念, 导致了许多人在赋值的时候经常出现 bug. 原因就是上面这样. 

    你 get from prototype 不代表就是 set to prototype, 因为属性是可以动态添加的. 你以为是 set, 结果变成了 new property.

    Function

    介绍

    JavaScript 的 Function 比较混乱. 尤其是 es6 之前. 因为它有双重身份.

    第一个身份是直观的函数. 调用, 传参数, 获取返回值.

    第二个身份是充当面向对象的 class. 这个比较不好理解.

    这一 part 我们主要先看看它的第一个身份. class 的部分下一个 part 才讲. (不然很乱的...)

    函数出没的地方

    函数是一等公民, 你可以直接定义它

    function myFunction1(param1){
      return 'return value';
    }

    可以 assign 给变量

    const myFunction2 = function (param1){
      return 'return value';
    };

    可以当参数传

    [].map(function (value, index) {
      return 'value';
    });

    可以当函数返回值

    function myFunction1() {
      return function () {};
    }

    可以当对象的方法

    const obj = {
      method: function (param1) {
        return 'value';
      },
    };

    宽松的参数数量

    参数的数量是没有严格定义的.

    举例, 函数声明了一个参数, 但是调用时传入超过 1 个参数是 ok 的

    function myFunction1(param1) {
      console.log(param1); // 1
    }
    myFunction1(1, 2, 3);

    而且参数是 optional 呢, 调用时没有传参数也是 ok 的

    function myFunction1(param1) {
      console.log(param1); // undefined
    }
    myFunction1();
    myFunction1(undefined); // 和上一行是等价的

    参数的默认值 (es6)

    function myFunction1(param1 = 'default value') {
      console.log(param1); 
    }
    myFunction1(undefined); // log 'default value'
    myFunction1();          // log 'default value'
    myFunction1('value');   // log 'value'

    用等于设置 defualt value

    arguments 对象

    当参数数量不固定时, 可以通过 arguments 获取最终调用者传入的所以参数信息

    function myFunction1(param1 = 'value') {
      console.log(arguments[0]); // a
      console.log(arguments[1]); // b
      console.log(arguments[2]); // c
      console.log(arguments.length); // 3
    }
    
    myFunction1('a', 'b', 'c');
    myFunction1(); // arguments.length = 0 (arguments 不看 default value)

    类似 C# 的 params 关键字

    宽松的返回值

    即使函数没有返回值, 但调用者还是可以把它 assign 给变量, 默认的返回值是 undefined. 这个和参数 undefined 概念是同样的.

    function myFunction1() {}
    const returnValue = myFunction1(); // undefined

    method 中的 this

    对象的成员是函数的话, 我们会把称它为方法 (method). 它和普通函数有一点点的不一样.

    const obj = {
      value: 'value',
    
      method1: function () {
        console.log(this.value); // value
      },
    };

    method 中的 this 指向的是当前的对象 obj. 这有一点点 class 的概念了. 

    在一些 build-in 的接口中也可以看到这类用法

    onreadystatechange 是 xhttp 对象的 method, method 中的 this 指向的是 xhttp 对象.

    this 的 偷龙转风 call & apply & bind

    method 中的 this 是可以通过一些手法调换的. 比如下面这样

    const obj = {
      value: 'value',
    
      method1: function () {
        console.log(this.value); // another value
      },
    };
    const otherObj = { value: 'another value' };
    obj.method1.call(otherObj);

    使用 call, apply, bind 都可以在调用 method 的时候传入一个对象来替代 this. 这是一种灵活的玩法. 但也相对混乱. es6 以后越来越少看到这种玩法了.

    第一是作为函数

    就是拿来调用, 传参数, 拿返回值的函数.

    function myFunction(param1) {
      return 'return value';
    }

    第二是作为对象

    函数也是对象, 所以它有属性.

    function myFunction(param1) {
      return 'return value';
    }
    console.log('name', myFunction.name);
    console.log('toString', myFunction.toString());
    console.log(
      'prototype',
      Object.getPrototypeOf(myFunction) === Function.prototype
    );

    log

    第二是作为 class (这一 part 我主要讲作为函数的函数, class 的部分下一个 part 讲)

    es6 以后.

    作为函数的部分可以用箭头函数取代

    作为 class 的部分, 可以用 class 取代

    所以 es6 以后比较少看到 function 这个字眼了, 也避免了混乱, 但是 es6 class 只是语法糖来的哦, 它底层依然时 function 来的

    函数参数与返回

    函数的参数是没有规定的, 看看下面的例子

    function myFunction(arg0 = 'default value', arg1, arg2) {
      console.log(arguments); // [undefined, 2, 3, 4, 5];
      console.log(arg0); // default value
      return 'value';
    }
    myFunction(undefined, 2, 3, 4, 5);

    函数只声明了 3 个参数, 但是调用的时候你可以传入 5 个, 传多传少都可以.

    传少获取的时候值是 undefined, 传多可以忽略, 也可以通过 arguments 对象获取参数信息. 

    参数 + default value = optional 参数. 它不像 C# 那样强制你必须把 optional 放后面. 

    JS 在调用函数时, 传入 undefined 就表示没有传入一样. 下面 2 句是等价的

    myFunction();
    myFunction(undefined, undefined, undefined, undefined);

    返回值也是 optional 的, 没有返回, 调用者会获取到 undefined 值.

    总结: 参数, 返回值 undefined 就表示没有传入参数和没有返回值.

    es6 函数参数

    arguments 虽然厉害但是也相对不好理解. es6 会用 rest parameters 来取代 arguments 的作用.

    function myFunction(arg0 = 'default', ...otherArgs) {
      console.log(arg0); // default
      console.log(otherArgs); // [1, 2]
      return 'value';
    }
    myFunction(undefined, 1, 2);

    另外箭头函数内是没有 arguments 对象的哦.

  • 相关阅读:
    TinyMCE下载及使用
    正则表达式30分钟入门教程
    JQuery插件官网汇总
    析构函数和Dispose的使用区别
    SlidesJS基本使用方法和官方文档解释 【Jquery幻灯片插件 Jquery相册插件】
    SlidesJS基本使用方法和官方文档解释 【Jquery幻灯片插件 Jquery相册插件】
    jQuery .tmpl(), .template()学习
    IIS请求筛选模块被配置为拒绝超过请求内容长度的请求
    前端小技巧
    CKEditor图片上传实现详细步骤(使用Struts 2)
  • 原文地址:https://www.cnblogs.com/keatkeat/p/16245499.html
Copyright © 2020-2023  润新知