• 【JS笔记】JSON对象和面向对象编程


    JSON 对象

    1 JSON格式

    • JavaScript Object Notation(符号;记号;表示法),是一种用于数据交换的文本格式
    • 相比 XML 格式,JSON 格式有两个显著的优点:1.书写简单,一目了然;2.符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。
    • 每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一个值,不能是两个或更多的值。
    • JSON 对值的类型格式有严格的规定。
    复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
    
    原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinityundefined)。
    null、空数组和空对象都是合法的 JSON 值。
    
    字符串必须使用双引号表示,不能使用单引号。
    
    对象的键名必须放在双引号里面。
    
    数组或对象最后一个成员的后面,不能加逗号。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2 JSON 对象

    • JSON对象是 JavaScript 的原生对象,用来处理 JSON 格式数据。
    • 它有两个静态方法:JSON.stringify()JSON.parse()

    3 JSON.stringify()

    3.1 基本用法

    • 将一个值转为JSON字符串。(该字符串符合JSON格式,且可以被JSON.parse()方法还原。)
    • 对于原始类型的字符串,转换结果会带双引号。比如 'foo' 会被转化为 "\"foo\"",这是为了后续还原时也能正确转为字符串。(外层可以是双引号,也可以是单引号的)
    • 如果对象的属性是undefined、函数或 XML 对象,该属性会被JSON.stringify()过滤。
    • 如果数组的成员是undefined、函数或 XML 对象,则这些值被转成null。
    • 正则对象会被转成空对象。
    • 会忽略对象的不可遍历属性

    3.2 第二个参数

    • JSON.stringify()方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。
      • 这个类似白名单的数组,只对对象的属性有效,对数组无效。
    • 第二个参数还可以是一个函数,用来更改JSON.stringify()的返回值。一般就会递归处理。

    3.3 第三个参数

    • JSON.stringify()还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。
    • 优化显示格式。可加 ‘\t’ (分行显示)、1-10数字(表示属性前添加的空格)(0添加也没啥用。大于10就还是10个空格)

    3.4 参数对象的 toJSON() 方法

    • 如果参数对象有自定义的toJSON()方法,那么JSON.stringify()会使用这个方法的返回值作为参数,而忽略原对象的其他属性。(就是有toJSON方法时,只看它了)
    • toJSON()方法的一个应用是,将正则对象自动转为字符串。
      • 因为JSON.stringify()默认不能转换正则对象,但是设置了toJSON()方法以后,就可以转换正则对象了。在这里插入图片描述

    4. JSON.parse()

    • JSON.parse()方法用于将 JSON 字符串转换成对应的值。
    • 为了处理解析错误,可以将JSON.parse()方法放在try...catch代码块中。
    • JSON.parse()方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify()方法类似。

    面向对象编程

    实例对象与 new 命令

    1 对象是什么

    • 每一个对象都是功能中心。对象可以复用,通过继承机制还可以定制。
    • 面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。
    • 对象是单个实物的抽象;
    • 对象是一个容器,封装了属性(property,对象的状态)和方法(method,对象的行为)。

    2 构造函数

    • JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
    • 所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构
    • 为了与普通函数区别,构造函数名字的首字母通常大写
    • 构造函数的特点有两个。
      1、函数体内部使用了this关键字,代表了所要生成的对象实例。
      2、生成对象的时候,必须使用new命令。

    3 new 命令

    3.1 基本用法

    • 作用:执行构造函数,返回一个实例对象

    • 使用new命令时,根据需要,构造函数也可以接受参数。

    • new命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。但为了表示这里是函数调用,推荐使用括号

    • 如果忘了使用new命令,直接调用构造函数,会发生什么事?

    • 这时,构造函数就变成了普通函数,并不会生成实例对象。this这时代表全局对象,将造成一些意想不到的结果。

    • 为了保证构造函数必须与new命令一起使用:

      • 解决办法1、构造函数内部使用严格模式,即第一行加上use strict。这样的话,一旦忘了使用new命令,直接调用构造函数就会报错。
      • 解决办法2、构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。
        感觉还是第二种方法比较好点。在这里插入图片描述

    3.2 new 命令的原理

    • 使用new命令时,它后面的函数依次执行下面的步骤。
    创建一个空对象,作为将要返回的对象实例。
    将这个空对象的原型,指向构造函数的prototype属性。
    将这个空对象赋值给函数内部的this关键字。
    开始执行构造函数内部的代码。
    
    • 1
    • 2
    • 3
    • 4
    • 构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。
    • 如果构造函数内部有return,且return一个对象,那么new命令返回的不是构造的This对象,而是return里的对象。
    • 对普通函数(内部没有this关键词的函数)使用New命令,返回一个空对象。

    3.4 new.target

    • 函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。
    • 作用:判断函数调用的时候,是否使用new命令。(跟上面的确保函数用New命令调用的内部判断法 类似)

    4 Object.create() 创建实例对象

    • 没有构造函数,只有一个现有的对象。用Object.create()方法,以这个现有的对象作为模板,生成新的实例对象。

    this 关键字

    1.涵义

    • this关键字是一个非常重要的语法点。
    • 简单说,this就是属性或方法“当前”所在的对象。因为This的指向是动态的,所以才会让人感到困惑。

    2.实质

    • 原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象,属性的值保存在属性描述对象的value里。
    • 属性的值有可能是一个函数,函数可以在不同的环境中执行===>this可以在函数体内部,获得当前的运行环境。

    3.使用场合

    • 1.全局环境:此时的this = window(顶层对象)【不管是否是函数内部
    • 2.构造函数:此时this指的是实例对象。
    • 3.对象的方法:
      • 如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。
      • 该方法赋值给另一个对象,就会改变this的指向。
      • (哪个地址去调用另个地址,哪个地址就是this指向的地址);this实在很有局部变量的感觉。

    4.使用注意点

    4.1避免多层 this。

     var o = {
      f1: function () {
        console.log(this);
        var f2 = function () {
          console.log(this);
        }();
      }
    }
    
    o.f1()
    // Object
    // Window
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 上面代码包含两层this,结果运行后,第一层指向对象o,第二层指向全局对象(不管是否在函数内部,全局环境下的就是指向window)
    • 解决方法:1.可以在第二层改用一个指向外层this的变量。that = this用一个变量固定This的值,内层函数调用这个变量。
      2. 使用严格模式,硬性避免这个问题。(会报错)【也不失为一种好的提示】

    4.2 避免数组处理方法中的 this

    • 数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
    • 多层this里层的那个this,不指向外部,而指向顶层。(跟4.1其实是一样的)
    • 解决方法:1、使用中间变量固定this
      2、将this当作foreach方法的第二个参数,固定它的运行环境。

    4.3 避免回调函数中的 this

    • 回调函数中的this往往会改变指向,最好避免使用。在这里插入图片描述

    5.绑定 this 的方法

    • JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。

    5.1 Function.prototype.call()

    • 函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
    • call方法的参数应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。
    var n = 123;
    var obj = { n: 456 };
    
    function a() {
      console.log(this.n);
    }
    
    a.call() // 123
    a.call(null) // 123
    a.call(undefined) // 123
    a.call(window) // 123
    a.call(obj) // 456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ~~好神奇有趣~

    • 如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。
    • call方法还可以接受多个参数。
    func.call(thisValue, arg1, arg2, ...)
    
    • 1
    • call的第一个参数就是this所要指向的那个对象后面的参数则是函数调用时所需的参数。

    • call方法的一个应用是调用对象的原生方法。

      • 无论原生方法在这个对象上有没有同名方法,都不会影响结果。Object.prototype.xx方法.call(对象名, 参数)

    5.2 Function.prototype.apply()

    • apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。
    func.apply(thisValue, [arg1, arg2, ...])
    
    • 1
    • apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。

    • 第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。

    • 有趣的应用
      1.找出数组最大元素

    var a = [10, 2, 4, 15, 9];
    Math.max.apply(null, a) // 15
    
    • 1
    • 2

    2.将数组的空元素变为undefined

    Array.apply(null, ['a', ,'b'])
    // [ 'a', undefined, 'b' ]
    
    • 1
    • 2

    空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。

    所以使用apply的话,可以让数组完整遍历。

    3.转换类似数组的对象

    • 利用数组对象的slice方法,可以将一个类似数组的对象(比如arguments对象)转为真正的数组。
    • 这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键
    Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
    Array.prototype.slice.apply({0: 1}) // []
    Array.prototype.slice.apply({length: 1}) // [undefined]
    
    • 1
    • 2
    • 3

    4.绑定回调函数的对象
    jQuery相关吧。

    5.3 Function.prototype.bind()

    • bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
      -bind()例子:
     var d = new Date();
    d.getTime() // 1481869925657
    
    var print = d.getTime;
    print() // Uncaught TypeError: this is not a Date object.赋值后getTime内部的this不指向Date实例了,所以会报错。
    
    var print = d.getTime.bind(d);
    print() // 1481869925657
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    一个比较好理解的,手动确定this执行环境的例子。
    在这里插入图片描述
    注意这个5是x参数呀,第二次调用时接收的是y参数。因为第一个参数是对象。后面的才是函数的参数。
    在这里插入图片描述

    • 如果bind()方法的第一个参数是nullundefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)。
    • bind可以绑定一个参数x。(2个参数时,后面运行时就只用提供y了)比起直接在函数里固定掉x,这种方法能让函数跟容易后续修改?
    • bind()方法有一些使用注意点。
      (1)每运行一次返回一个新函数
    • 所以不能直接把bind函数绑定在某个’click’上,这样会导致无法取消绑定。应该有一个变量,接受Bind方法,click对这个变量进行处理。
      (2)结合回调函数使用。
    • 回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含this的方法直接当作回调函数。
    • 回调函数里用严格模式,调用的时候用bind绑定环境。
    • 某些数组方法可以接受一个函数当作参数。这些函数内部的this指向,很可能也会出错。也要bind绑定环境
      (3)结合**call()**方法使用
    • 利用bind()方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的slice()方法为例。
      不太知道具体实践中会怎么用 = = .call.bind
      三者有点类似,也不知道实际使用中的区别 = =

    对象的继承

    • 在ES6里有基于class的继承,JS传统是通过原型对象(prototype)实现继承的。
    • 继承对于代码复用很有用。

    1.原型对象概述

    1.1 构造函数的缺点

    • 同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。解决这一问题,用原型对象。

    1.2 prototype属性的作用

    • JavaScript 继承机制的设计思想就是,prototype的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在prototype上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
      (好像有点理解了,之前的就是会在prototype上定义一些公共的属性和方法啥的)

    • 原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。

    • 因为,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
      (所以其实也挺好的,改一个,全部能应用上)
      首先有一个构造函数,然后呢,构造函数有些原型对象,这时候实例化,就可以用原型对象了。

    • 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。

    • 总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象

    1.3 原型链

    • 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。
    • 也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。
    • Object.prototype对象的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。(赛博世界的0生1,1生2,3生万物了)
    • 读取对象的某个属性时,JavaScript 引擎按照从近到远的顺序寻找。先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
    • 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
    • 一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
      (所以在布置寻找某个属性的任务时,最好确定它是存在的,不然确实浪费性能哦)

    1.4 constructor 属性

    • n. (尤指汽车或飞机的)建造者;构造函数;构造器;建构子
    • prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数
    • constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
    • 有了constructor属性,就可以从一个实例对象新建另一个实例。
      (就像找妈妈😀
    • 在实例方法中,也可以调用自身的构造函数。
    • 注意:修改原型对象时,一般要同时修改constructor属性的指向,否则会出错。如何修改比较好呢,有如下两种方法。
     // 好的写法:将constructor属性重新指向原来的构造函数。再修改。
    C.prototype = {
      constructor: C,
      method1: function (...) { ... },
      // ...
    };
    
    // 更好的写法:只在原型上添加方法,不做其他的改动。
    C.prototype.method1 = function (...) { ... };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    因为其实如果按“好的写法”而不加重新指向的话,相当于重新定义了一次了。那肯定就有问题哦。

    2 instanceof运算符

    • instance:实例;情况;事例;例证;要求;场合;建议;诉讼手续;举…为例;引以为例;举例证明
    • instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
    • isPrototypeOf()方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型
     v instanceof Vehicle
    // 等同于
    Vehicle.prototype.isPrototypeOf(v)
    
    • 1
    • 2
    • 3

    ~~ 从翻译来看就是: x 是不是 X 的实例呢?是;否~~
    X是不是x的原型呢?是;否

    • 由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true。

    • 由于任意对象(除了null)都是Object的实例,所以instanceof运算符可以判断一个值是否为非null的对象。

      • 唯一特例:如果左边对象的原型链上只有null对象,那么这是instanceof就失真了(belike Object 的原型是null.此时用instanceof判断,却是false.
    • instanceof运算符的一个用处,是判断值的类型。(仅限于对象,字符串(原始类型)不是对象(也不是String对象的实例),所以不行啦)

    • 利用instanceof运算符可以解决:调用构造函数时,忘了加new命令的问题。

    function Fubar (foo, bar) {
      if (this instanceof Fubar) {
        this._foo = foo;
        this._bar = bar;
      } else {
        return new Fubar(foo, bar);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    instanceof运算符,在函数体内部判断this关键字是否为构造函数Fubar的实例。如果不是,就表明忘了加new命令。

    3 构造函数的继承

    -1、完整继承的方法:
    让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。第一步是在子类的构造函数中,调用父类的构造函数。

     function Sub(value) {
      Super.call(this);
      this.prop = value;
    }
    
    • 1
    • 2
    • 3
    • 4

    上面代码中,Sub是子类的构造函数,this是子类的实例。在实例上调用父类的构造函数Super,就会让子类实例具有父类实例的属性。
    (Super.call(this)就是调用啊?call可以绑定this莫)
    第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。

    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    Sub.prototype.method = '...';
    
    • 1
    • 2
    • 3

    上面代码中,要将Sub.prototype赋值为Object.create(Super.prototype),而不是直接等于Super.prototype。否则后面两行对Sub.prototype的操作,会连父类的原型Super.prototype一起修改掉。(涉及到内部的this的指向?
    另一种将Sub.prototype = 一个父类实例的写法就达咩。子类会有父类实例的方法,这有时不是我们需要的。所以不推荐。

    • 2、单个方法的继承:
    ClassB.prototype.print = function() {
      ClassA.prototype.print.call(this);
      // some code
    }
    
    • 1
    • 2
    • 3
    • 4

    上面代码中,子类B的print方法先调用父类A的print方法,再部署自己的代码。这就等于继承了父类A的print方法。

    4 多重继承

    • JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。
    • 先call,在Object.create,要继承多个的话,就再Object.assign,再指定构造函数constructor,这样就可以继承多个对象啦。例子如下。
     function M1() {
      this.hello = 'hello';
    }
    
    function M2() {
      this.world = 'world';
    }
    
    function S() {
      M1.call(this);
      M2.call(this);
    }
    
    // 继承 M1
    S.prototype = Object.create(M1.prototype);
    // 继承链上加入 M2
    Object.assign(S.prototype, M2.prototype);
    
    // 指定构造函数
    S.prototype.constructor = S;
    
    var s = new S();
    s.hello // 'hello'
    s.world // 'world'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这种模式又称为:Mixin

    5 模块

    • 开发者必须使用软件工程的方法,管理网页的业务逻辑。
    • 理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
    • 但是,JavaScript 不是一种模块化编程语言,ES6 才开始支持“类”和“模块”。下面介绍传统的做法,如何利用对象实现模块的效果。

    5.1 基本的实现方法

    • 模块是实现特定功能的一组属性和方法的封装。
    • 简单的做法是把模块写成一个对象,所有的模块成员都放到这个对象里面。
      缺点:这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

    5.2 封装私有变量:构造函数的写法

    • 可以利用构造函数,封装私有变量。
      缺点:这种方法将私有变量封装在构造函数中,导致构造函数与实例对象是一体的,总是存在于内存之中,无法在使用完成后清除。
      (这意味着,构造函数有双重作用,既用来塑造实例对象,又用来保存实例对象的数据,违背了构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不应该保存在实例对象以外)。同时,非常耗费内存。)

    5.3 封装私有变量:立即执行函数的写法

    • 将相关的属性和方法封装在一个函数作用域里面,可以达到不暴露私有成员的目的。
    • 如下是JS模块的基本写法。
     var module1 = (function () {
     var _count = 0;
     var m1 = function () {
       //...
     };
     var m2 = function () {
      //...
     };
     return {
      m1 : m1,
      m2 : m2
     };
    })();
    使用上面的写法,外部代码无法读取内部的_count变量。
    console.info(module1._count); //undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.4 模块的放大模式

    • 一个大模块要分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。
    • “宽放大模式”:“立即执行函数”的参数可以是空对象。

    5.5 输入全局变量

    • 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。

    为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
    例子:

    var module1 = (function ($, YAHOO) {
     //...
    })(jQuery, YAHOO);
    
    • 1
    • 2
    • 3

    把这两个库(其实是两个模块)当作参数输入module1。
    这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

    Object 对象的相关方法

    (很多之前提过的)

    1. Object.getPrototypeOf()

    • 返回参数对象的原型。这是获取原型对象的标准方法
    • 几个特殊对象的原型
      • 空对象的原型是 Object.prototype
      • Object.prototype 的原型是 null
      • 函数的原型是 Function.prototype

    2. Object.setPrototypeOf()

    • 为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。这样现有对象可以共享原型对象的属性了。
    • new命令的原理:new命令可以使用Object.setPrototypeOf方法模拟。
    var F = function () {
      this.foo = 'bar';
    };
    
    var f = new F();
    // 等同于
    var f = Object.setPrototypeOf({}, F.prototype);
    F.call(f);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面代码中,new命令新建实例对象,其实可以分成两步。
    第一步,将一个空对象的原型设为构造函数的prototype属性(上例是F.prototype);第二步,将构造函数内部的this绑定这个空对象,然后执行构造函数,使得定义在this上面的方法和属性(上例是this.foo),都转移到这个空对象上。

    3 Object.create()

    • 该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
    • 该方法的实质是新建一个空的构造函数F,然后让F.prototype属性指向参数对象obj,最后返回一个F的实例,从而实现让该实例继承obj的属性。

    生成新对象的三种方式 完全等价

    var obj1 = Object.create({});
    var obj2 = Object.create(Object.prototype);
    var obj3 = new Object();
    
    • 1
    • 2
    • 3
    • 如果想要生成一个不继承任何属性(比如没有toString()和valueOf()方法)的对象,可以将Object.create()的参数设为null。
    • 使用Object.create()方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。
    • Object.create()方法生成的对象,继承了它的原型对象的构造函数。

    4.Object.prototype.isPrototypeOf()

    • 该方法可用来判断该对象是否为参数对象的原型。

    5. Object.prototype.proto

    • 实例对象的__proto__属性(前后各两个下划线),返回该对象的原型。该属性可读写。
    • 其实功能跟前面的Object.getPrototypeOf()Object.setPrototypeOf() 差不多的。也算是被逐渐替代掉的一个?
    • __proto__属性只有浏览器才需要部署,其他环境可以没有这个属性。
    • 它前后的两根下划线,表明它本质是一个内部属性,不应该对使用者暴露。

    6. 获取原型对象方法的比较

    获取实例对象obj的原型对象,有三种方法。

    obj.__proto__
    obj.constructor.prototype
    Object.getPrototypeOf(obj)
    
    • 1
    • 2
    • 3

    上面三种方法之中,前两种都不是很可靠。
    __proto__属性只有浏览器才需要部署,其他环境可以不部署。
    而obj.constructor.prototype在手动改变原型对象时,可能会失效。(就是用这个方法的话改变原型对象,还要同时把这个属性给重新定义一下,不然是不会生效的,前面也有提到过)
    所以第三种是推荐的。

    7.Object.getOwnPropertyNames()

    • Object.getOwnPropertyNames方法返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名。(不管是否可以遍历)
      • 只获取那些可以遍历的属性,使用Object.keys方法。

    8.Object.prototype.hasOwnProperty

    • 返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。
    • Date.length(构造函数Date可以接受多少个参数)是Date自身的属性
    • hasOwnProperty方法是 JavaScript 之中唯一一个处理对象属性时,不会遍历原型链的方法。(就是只判断有无,有了就立马返回的意思吧…难道其他的都是要遍历一遍才行的嘛…)

    9. in 运算符和 for…in 循环

    • in运算符返回一个布尔值,表示一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性。
    • 常用于检查一个属性是否存在。
    • 获得对象的所有可遍历属性(不管是自身的还是继承的),可以使用for…in循环。
    • 为了在for…in循环中获得对象自身的属性,可以采用hasOwnProperty方法判断一下。(用个if)
    • 获得对象的所有属性(不管是自身的还是继承的,也不管是否可枚举),可以使用下面的函数。
    function inheritedPropertyNames(obj) {
      var props = {};
      while(obj) {
        Object.getOwnPropertyNames(obj).forEach(function(p) {
          props[p] = true;
        });
        obj = Object.getPrototypeOf(obj);
      }
      return Object.getOwnPropertyNames(props);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面代码依次获取obj对象的每一级原型对象“自身”的属性,从而获取obj对象的“所有”属性,不管是否可遍历。(到了最后一级就是Object,也会被遍历到)

    10.对象的拷贝

    • 如果要拷贝一个对象,需要做到下面两件事情。

    确保拷贝后的对象,与原对象具有同样的原型。
    确保拷贝后的对象,与原对象具有同样的实例属性。

    例子有点点复杂= =

    另一种更简单的写法,是利用 ES2017 才引入标准的
    Object.getOwnPropertyDescriptors方法。

    function copyObject(orig) {
      return Object.create(
        Object.getPrototypeOf(orig),
        Object.getOwnPropertyDescriptors(orig)
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    严格模式

    • 除了正常的运行模式,JavaScript 还有第二种运行模式:严格模式(strict mode)。顾名思义,这种模式采用更加严格的 JavaScript 语法。
    • 严格模式是从 ES5 进入标准的
    • 进入严格模式的标志,是一行字符串use strict。
    • 严格模式可以用于整个脚本,也可以只用于单个函数。
    • use strict放在脚本文件的产生实际运行结果的语句的最前面。use strict可以直接跟在一个空的分号后面,或者跟在注释后面。
    • 和非严格模式的脚本合并,直接合并容易出错(会以在前面的模式为准,结果都是不正确的),解决方法:把整个脚本文件放在一个立即执行的匿名函数之中。
      (其实搞不懂为啥这样就可以解决了= =)

    显式报错

    • 语法更严格,更多的操作会显示报错。(很多之前也有提到了)
      比如:
    • 严格模式下,对只读属性赋值,或者删除不可配置(non-configurable)属性都会报错。
    • 严格模式下,对一个只有取值器(getter)、没有存值器(setter)的属性赋值,会报错。
    • 严格模式下,对禁止扩展的对象添加新属性,会报错。
    • 严格模式下,使用eval或者arguments作为标识名,将会报错。下面的语句都会报错。
    • 严格模式下,函数不能有重名的参数
    • 禁止八进制的前缀0表示法

    增强的安全措施

    • 全局变量也要显式声明,变量都必须先声明,然后再使用。
    • 禁止 this 关键字指向全局对象(这种限制对于构造函数尤其有用。使用构造函数时,有时忘了加new,这时this不再指向全局对象,而是报错。)
      一些区别:
    // 正常模式
    function fun() {
      return this;
    }
    
    fun() // window
    fun.call(2) // Number {2}
    fun.call(true) // Boolean {true}
    fun.call(null) // window
    fun.call(undefined) // window
    
    // 严格模式
    'use strict';
    function fun() {
      return this;
    }
    
    fun() //undefined
    fun.call(2) // 2
    fun.call(true) // true
    fun.call(null) // null
    fun.call(undefined) // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 禁止使用 fn.callee、fn.caller,意味着不能在函数内部得到调用栈了。
    • 禁止使用 arguments.callee、arguments.caller,两个历史遗留的变量,也没啥用。
    • 禁止删除变量:只有对象的属性,且属性的描述对象的configurable属性设置为true,才能被delete命令删除。

    静态绑定

    JavaScript 语言的一个特点,就是允许“动态绑定”,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。

    严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,必须在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。

    • 禁止使用 with 语句,因为with语句无法在编译时就确定,某个属性到底归属哪个对象,从而影响了编译效果。
    • 创设 eval 作用域(第三种作用域),eval所生成的变量只能用于eval内部。可以在eval作用域里加‘use strict’,这样它内部使用的也是严格模式了。
    • arguments 不再追踪参数的变化,改变函数的参数,不会反应到arguments对象上来。

    向ES6过渡

    • 非函数代码块不得声明函数 。如果是 ES6 环境允许在代码块之中声明函数。
    • 增加了保留字,(implements、interface、let、package、private、protected、public、static、yield等)。使用这些词作为变量名将会报错。
  • 相关阅读:
    Vue2 Element description组件 列合并
    20. 【Linux教程】emacs 编辑器
    PHP 程序员是学 Swoole ?还是学 Go ?
    mysql集群的主从复制搭建
    由[哈希/散列]模拟实现[unordered_map/unordered_set] (手撕迭代器)
    单机部署
    mycat的部署及测试
    pytest --version报错
    标准化助推开源发展丨九州未来参编开源领域4项团体标准正式发布
    论文相关知识:扁平化设计
  • 原文地址:https://blog.csdn.net/sinat_41838682/article/details/125383984