易师傅 發表於 2020-8-12 15:53:00

《JavaScript语言入门教程》记录整理:面向对象

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>实例对象与 new 命令</li><li>this关键字</li><li>对象的继承</li><li><code>Object</code>对象的方法</li><li>严格模式(<code>strict mode</code>)</li></ul></div><p></p>
<p>本系列基于阮一峰老师的《JavaScrip语言入门教程》或《JavaScript教程》记录整理,教程采用知识共享 署名-相同方式共享 3.0协议。这几乎是学习js最好的教程之一(去掉之一都不过分)</p>
<p>最好的教程而阮一峰老师又采用开源方式共享出来,之所以重新记录一遍,一是强迫自己重新认真读一遍学一遍;二是对其中知识点有个自己的记录,加深自己的理解;三是感谢这么好的教程,希望更多人阅读了解</p>
<h1 id="实例对象与-new-命令">实例对象与 new 命令</h1>
<ol>
<li>
<p>面向对象编程(<code>Object Oriented Programming</code>,<code>OOP</code>)将现实世界中的实物、逻辑操作及各种复杂关系抽象为一个个对象,每一个对象完成一定的功能,用来接受信息、处理数据或执行操作、发布信息等,通过继承还能实现复用和功能扩展。比起由一系列函数或指令组成的传统的过程式编程(<code>procedural programming</code>)更适合大型项目。</p>
</li>
<li>
<p>什么是"对象"(<code>object</code>):(1)对象是单个实物的抽象。(2)对象是一个容器,封装了属性(property)和方法(method)。属性是对象的状态,方法是对象的行为(完成某种任务)。</p>
</li>
<li>
<p>生成对象时,通常需要一个模板,表示某一类实物的共同特征,然后根据模板生成。在C++、java、c#等语言中都有类(class)的概念。"类"就是对象的模板,对象是"类"的实例(即类的一个具体对象)。JavaScript的对象体系基于构造函数(<code>constructor</code>)和原型链(<code>prototype</code>)构成。</p>
</li>
<li>
<p>JavaScript 语言中构造函数(<code>constructor</code>)就是对象的模板,描述实例对象的基本结构。"构造函数"就是专门用来生成实例对象的函数。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。</p>
</li>
<li>
<p>构造函数和普通函数一样,但是有自己的特征和用法。</p>
</li>
</ol>
<p>如下,<code>Vehicle</code>就是构造函数。通常构造函数名字第一个字母大写(与普通函数作区分)。</p>
<pre><code class="language-js">var Vehicle = function () {
this.price = 1000;
};
</code></pre>
<p><strong>构造函数的特点</strong>:</p>
<ul>
<li>函数体内部使用了<code>this</code>关键字,代表了所要生成的对象实例。</li>
<li>生成对象的时候,必须使用<code>new</code>命令。</li>
</ul>
<ol start="6">
<li><code>new</code>命令的作用是执行构造函数,返回一个实例对象。</li>
</ol>
<pre><code class="language-js">var Vehicle = function () {
this.price = 1000;
};

var v = new Vehicle();
v.price // 1000
</code></pre>
<p><strong>如果忘记了new命令,就成了构造函数作为普通函数直接调用</strong></p>
<p>为了保证构造函数必须使用new命令,解决办法有两种:</p>
<p>一、可以在构造函数内部使用严格模式。这样不使用new命令直接调用就会报错</p>
<pre><code class="language-js">var Vehicle = function () {
'use strict';
this.price = 1000;
};

var v = Vehicle();// Uncaught TypeError: Cannot set property 'price' of undefined
</code></pre>
<blockquote>
<p>严格模式中,函数内部的<code>this</code>不能指向全局对象,默认等于<code>undefined</code>,导致不加<code>new</code>调用会报错</p>
</blockquote>
<p>二、在构造函数内部判断是否使用<code>new</code>命令,如果没有,则根据参数返回一个实例对象。</p>
<pre><code class="language-js">function Vehicle(price) {
if (!(this instanceof Vehicle)) {
    return new Vehicle(price);
}

this.price = price||1000;
};

var v1 = Vehicle();
var v2 = new Vehicle();
</code></pre>
<ol start="7">
<li>使用<code>new</code>命令时,后面的函数依次执行下面的步骤。</li>
</ol>
<ul>
<li>创建一个空对象,作为将要返回的对象实例。</li>
<li>将这个空对象的原型,指向构造函数的<code>prototype</code>属性。</li>
<li>将这个空对象赋值给函数内部的this关键字。</li>
<li>开始执行构造函数内部的代码。</li>
</ul>
<p>构造函数内部,<code>this</code>指的是一个新生成的空对象。构造函数的目的就是操作一个空对象(即<code>this</code>对象),将其"构造"为需要的样子。</p>
<blockquote>
<p>如果构造函数内部有return语句且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。</p>
<pre><code class="language-js">var Vehicle = function () {
this.price = 1000;
return 1000;// 忽略非对象的return语句
};

(new Vehicle()) === 1000
</code></pre>
<p>如果return返回的是其他对象而不是this,那么new命令将会返回这个新对象</p>
<p>如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。</p>
<pre><code class="language-js">function getMessage() {
return 'this is a message';
}

var msg = new getMessage();
msg // {}
typeof msg // "object"
</code></pre>
</blockquote>
<blockquote>
<p><code>new</code>命令简化的内部流程,可用下面的代码表示。</p>
<pre><code class="language-js">function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' &amp;&amp; result != null) ? result : context;
}

// 实例
var actor = _new(Person, '张三', 28);
</code></pre>
</blockquote>
<ol start="8">
<li>函数内部的<code>new.target</code>属性。如果当前函数是new命令调用,<code>new.target</code>指向当前函数,否则为<code>undefined</code>。</li>
</ol>
<pre><code class="language-js">function f() {
console.log(new.target === f);
}

f() // false
new f() // true
</code></pre>
<p>此属性可判断是否使用new命令调用了函数</p>
<pre><code class="language-js">function f() {
if (!new.target) {
    throw new Error('请使用 new 命令调用!');
}
// ...
}

f() // Uncaught Error: 请使用 new 命令调用!
</code></pre>
<ol start="9">
<li><code>Object.create()</code> 创建实例对象</li>
</ol>
<p>通常使用构造函数作为生成实例对象的模板。但是如果没有构造函数只有对象时,可以使用<code>Object.create()</code>方法以一个对象作为模板,生成新的实例对象。</p>
<p>如下,对象<code>person1</code>是<code>person2</code>的模板,后者继承了前者的属性和方法。</p>
<pre><code class="language-js">var person1 = {
name: '张三',
age: 38,
greeting: function() {
    console.log('你好,我是' + this.name + '。');
}
};

var person2 = Object.create(person1);
person2.name;      // "张三"
person2.name="李四"// "李四"
person2.greeting()   // 你好,我是李四。

person1.greeting()// 你好,我是张三。
</code></pre>
<h1 id="this关键字">this关键字</h1>
<ol>
<li><code>this</code>关键字总是返回一个对象,或指向一个对象。</li>
<li><code>this</code>就是属性或方法"当前"所在的对象。也就是说,如果改变属性或方法所在的对象,就可以改变this的指向</li>
</ol>
<p>将对象的属性赋给另一个对象,改变属性所在对象,可以改变this的指向。</p>
<p>如下,通过改变函数<code>f</code>所在的对象,实现this的改变</p>
<pre><code class="language-js">function f() {
return '姓名:'+ this.name;
}

var A = {
name: '张三',
describe: f
};

var B = {
name: '李四',
describe: f
};

f()          // "姓名:"
A.describe() // "姓名:张三"
B.describe() // "姓名:李四"
</code></pre>
<p><strong>只要函数被赋给另一个变量,this的指向就会变。</strong></p>
<ol start="4">
<li>
<p>JavaScript中,一切皆对象。运行环境也是对象(顶层函数中,this指向window对象),函数都是在某个对象之中运行,<code>this</code>就是函数运行时所在的对象(环境)。同时this的指向是动态的</p>
</li>
<li>
<p><code>this</code>的本质或<code>this</code>的设计目的:</p>
</li>
</ol>
<p>js的对象在内存的结构是这样的,对象存在堆中,当把对象赋值给一个变量时,实际是将对象在堆中的内存地址赋值给变量。如下,将对象的地址(<code>reference</code>)赋值给变量obj</p>
<pre><code class="language-js">var obj = { foo:5 };
</code></pre>
<p>读取<code>obj.foo</code>的过程是,先从obj拿到内存地址,然后从该地址读出原始的对象,返回它的<code>foo</code>属性</p>
<p>原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。比如上面的属性<code>foo</code>实际保存形式如下,<code>foo</code>属性的值保存在属性描述对象的<code>value</code>属性里面:</p>
<pre><code class="language-js">{
foo: {
    []: 5
    []: true
    []: true
    []: true
}
}
</code></pre>
<p>当属性的值是函数时</p>
<pre><code class="language-js">var obj = { foo: function () {} };
</code></pre>
<p>js将函数单独保存在内存中,将函数的地址赋值给<code>foo</code>属性的<code>value</code>属性。</p>
<pre><code class="language-js">{
foo: {
    []: 函数的地址
    ...
}
}
</code></pre>
<p>因为函数是单独存在的值,所以可以在不同的环境(上下文)执行</p>
<p>JavaScript允许在函数体内部,引用当前环境的其他变量。</p>
<p>如下,函数体使用的变量x由运行环境提供。</p>
<pre><code class="language-js">var f = function () {
console.log(x);
};
</code></pre>
<p>由于函数可以在不同的运行环境执行,所以需要一种机制,可以<strong>在函数体内部获得当前的运行环境(context)</strong>。所以<code>this</code>就被用来设计为,<strong>在函数体内部,指代函数当前的运行环境</strong>。</p>
<p>如下,函数体中<code>this.x</code>就指当前运行环境的<code>x</code>。</p>
<pre><code class="language-js">var f = function () {
console.log(this.x);
}
</code></pre>
<ol start="6">
<li><code>this</code>的使用场合</li>
</ol>
<ul>
<li>全局环境使用<code>this</code>,指的是顶层对象<code>window</code>。</li>
<li>构造函数中的<code>this</code>,指的是实例对象。</li>
<li>对象的方法里面包含<code>this</code>,<code>this</code>的指向就是方法运行时所在的对象。该方法赋值给另一个对象,会改变<code>this</code>的指向。</li>
</ul>
<p>关于<code>this</code>的指向并不好把握,比如下面的例子</p>
<pre><code class="language-js">var obj ={
foo: function () {
    console.log(this);
}
};

obj.foo() // obj
</code></pre>
<p>如上,通过调用boj对象的foo方法,输出this为当前的obj对象。但是,如果使用下面的形式,都会改变this的指向</p>
<pre><code class="language-js">// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window
</code></pre>
<p>上面代码中,<code>obj.foo</code>是获取出来之后再调用,相当于一个值,这个值在调用的时候,运行环境已经从<code>obj</code>变为了全局环境,<code>this</code>的指向变为了<code>window</code></p>
<p>可以这样理解,在js引擎内部,<code>obj</code>对象和<code>obj.foo</code>函数储存在两个内存地址,称为地址一和地址二。<code>obj.foo()</code>调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,<code>this</code>指向<code>obj</code>。上面三种情况,都是直接取出地址二进行调用(即取出函数调用),这样的话,运行环境就是全局环境,<code>this</code>指向的是全局环境。上面三种情况等同于下面的代码:</p>
<pre><code class="language-js">// 情况一
(obj.foo = function () {
console.log(this);
})()
// 等同于
(function () {
console.log(this);
})()

// 情况二
(false || function () {
console.log(this);
})()

// 情况三
(1, function () {
console.log(this);
})()
</code></pre>
<p><code>this</code>所在的方法不在对象的第一层时,这时<code>this</code>指向当前一层的对象(即当前所在的对象),而不会继承更上面的层。</p>
<pre><code class="language-js">var a = {
p: 'Hello',
b: {
    m: function() {
      console.log(this.p);
    }
}
};

a.b.m() // undefined
</code></pre>
<ol start="7">
<li><code>this</code>使用中注意点:</li>
</ol>
<ul>
<li>避免多层<code>this</code>。用于<code>this</code>的指向可变,尽量不要在函数中包含多层this</li>
</ul>
<p>通过添加指向this的变量,实现多层this的使用</p>
<pre><code class="language-js">var o = {
f1: function() {
    console.log(this);
    var that = this;
    var f2 = function() {
      console.log(that);
    }();
}
}

o.f1()
// Object
// Object
</code></pre>
<p>JavaScript严格模式下,如果函数内部的<code>this</code>指向顶层对象,就会报错。</p>
<ul>
<li>避免使用数组处理方法(<code>map</code>和<code>foreach</code>方法中的参数函数)中的<code>this</code></li>
</ul>
<p><code>map</code>、<code>foreach</code>方法的回调函数中的<code>this</code>指向window对象。解决办法是使用一个中间变量固定this,或者使用<code>this</code>作为<code>map</code>、<code>foreach</code>方法的第二个参数</p>
<pre><code class="language-js">// 中间变量
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
}
}

o.f()
// hello a1
// hello a2

// 第二个参数this
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
}
}

o.f()
// hello a1
// hello a2
</code></pre>
<ul>
<li>回调函数中避免使用<code>this</code>(往往会改变指向)。</li>
</ul>
<ol start="8">
<li><code>this</code>的动态切换,既体现了灵活,又使编程变得困难和模糊。js提供了<code>call</code>、<code>apply</code>、<code>bind</code>方法,来切换/固定<code>this</code>的指向。</li>
<li><code>Function.prototype.call()</code>:<strong>函数实例</strong>的<code>call</code>方法,可以指定函数内部<code>this</code>的指向(即函数执行时所在的作用域),然后在指定的作用域中调用该函数</li>
</ol>
<p>如下,使用call改变作用域6</p>
<pre><code class="language-js">var obj = {};

var f = function () {
return this;
};

f() === window // true
f.call(obj) === obj // true
</code></pre>
<p><code>call</code>方法的第一个参数,应该是一个对象。如果参数为空、<code>null</code>和<code>undefined</code>,则this指向全局对象。</p>
<pre><code class="language-js">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
</code></pre>
<p><code>call</code>方法的第一个参数是一个原始值,则原始值会自动转成对应的包装对象,然后传入<code>call</code>方法。</p>
<pre><code class="language-js">var f = function () {
return this;
};

f.call(5)   // Number {[]: 5}
</code></pre>
<p><code>call</code>方法除第一个参数表示调用函数的作用域,其他参数以列表的形式传递,表示函数执行时的参数</p>
<pre><code class="language-js">func.call(thisValue, arg1, arg2, ...)
</code></pre>
<p><strong>call方法的一个应用是调用对象的原生方法。</strong></p>
<pre><code class="language-js">var obj = {};
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false
</code></pre>
<ol start="10">
<li><code>Function.prototype.apply()</code>:<code>apply</code>方法的作用,也是改变<code>this</code>指向,然后再调用该函数。但是它接收的是一个数组作为函数执行时的参数,</li>
</ol>
<pre><code class="language-js">func.apply(thisValue, )
</code></pre>
<p>和<code>call</code>一样,第一个参数是<code>this</code>指向的对象。null或undefined表示全局对象。第二个参数是数组,表示传入原函数的参数</p>
<blockquote>
<p><code>apply</code>数组,<code>call</code>列表</p>
</blockquote>
<p>(1)找出数组最大元素</p>
<p>js默认没有找出数组最大元素的函数,结合<code>apply</code>和<code>Math.max</code>可实现返回数组的最大元素</p>
<pre><code class="language-js">var a = ;
Math.max.apply(null, a) // 15
</code></pre>
<p>(2)将数组的空元素变为<code>undefined</code></p>
<p>结合<code>apply</code>和<code>Array</code>构造函数将数组的空元素变成<code>undefined</code>。</p>
<pre><code class="language-js">Array.apply(null, ['a', ,'b'])   // [ 'a', undefined, 'b' ]
</code></pre>
<blockquote>
<p><code>forEach</code>等循环方法会跳过空元素,但是不会跳过<code>undefined</code></p>
</blockquote>
<p>(3)转换类似数组的对象</p>
<p>利用数组对象的<code>slice</code>方法,可以将一个类似数组的对象(如<code>arguments</code>对象)转为真正的数组。</p>
<pre><code class="language-js">Array.prototype.slice.apply({0: 1, length: 1}) //
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) //
Array.prototype.slice.apply({length: 1}) // [空]
</code></pre>
<p>(4)绑定回调函数的对象</p>
<p>可以在事件方法等回调函数中,通过<code>apply</code>/<code>call</code>绑定方法调用的对象,修改this指向</p>
<pre><code class="language-js">var o = new Object();
o.f = function () {
console.log(this === o);
}

var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};

// jQuery 的写法
$('#button').on('click', f);
</code></pre>
<p>因为<code>apply()</code>/<code>call()</code>方法在绑定函数执行时所在的对象时,还会立即执行函数,因此需要把绑定语句写在一个函数体内。</p>
<ol start="11">
<li><code>Function.prototype.bind()</code>:<code>bind()</code>方法将函数体内的<code>this</code>绑定到某个对象,然后返回一个新函数。</li>
</ol>
<p>如下是一个通过赋值导致函数内部this指向改变的示例。</p>
<pre><code class="language-js">var d = new Date();
d.getTime() // 1596621203097

var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.
</code></pre>
<p>将<code>d.getTime</code>赋值给变量<code>print</code>后,方法内部的this由原来指向Date对象实例改为了window对象,<code>print()</code>执行报错。</p>
<p>使用<code>bind()</code>方法绑定函数执行的this指向,可以解决这个问题。</p>
<pre><code class="language-js">var print = d.getTime.bind(d);
undefined
print()   // 1596621203097
</code></pre>
<p><code>bind()</code>可接受更多参数,将这些参数绑定原函数的参数。</p>
<pre><code class="language-js">var add = function (x, y) {
return x * this.m + y * this.n;
}

var obj = {
m: 2,
n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5) // 20
</code></pre>
<p>如上,<code>bind()</code>方法除了绑定<code>this</code>对象,还绑定<code>add()</code>函数的第一个参数<code>x</code>为<code>5</code>,然后返回一个新函数<code>newAdd()</code>,这个函数只要再接受一个参数<code>y</code>就能运行了。</p>
<p><code>bind()</code>第一个参数是<code>null</code>或<code>undefined</code>时,<code>this</code>绑定的是全局对象(浏览器环境为<code>window</code>)</p>
<ol start="12">
<li><code>bind()</code>方法特定:</li>
</ol>
<ul>
<li>每一次返回一个新函数</li>
</ul>
<p>这就导致,如果绑定事件时直接使用<code>bind()</code>会绑定为一个匿名函数,导致无法取消事件绑定</p>
<pre><code class="language-js">element.addEventListener('click', o.m.bind(o));
// 如下取消是无效的
element.removeEventListener('click', o.m.bind(o));
</code></pre>
<p>正确写法:</p>
<pre><code class="language-js">var listener = o.m.bind(o);
element.addEventListener('click', listener);
//...
element.removeEventListener('click', listener);
</code></pre>
<ul>
<li>
<p>结合回调函数使用。将包含<code>this</code>的方法直接当做回调函数,会导致函数执行时改变了this的指向,从而出错。解决办法是使用<code>bind()</code>方法绑定回调函数的<code>this</code>对象。当然,也可使用中间变量固定<code>this</code></p>
</li>
<li>
<p>结合<code>call()</code>方法使用。改写一些JS原生方法的使用</p>
</li>
</ul>
<p>如下数组的slice方法</p>
<pre><code class="language-js">.slice(0, 1) //
// 等同于
Array.prototype.slice.call(, 0, 1) //
</code></pre>
<p><strong><code>call()</code>方法实质上是调用<code>Function.prototype.call()</code>方法。</strong></p>
<pre><code class="language-js">// 上面等同于
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(, 0, 1) //
</code></pre>
<p>相当于在<code>Array.prototype.slice</code>调用<code>Function.prototype.call</code>,参数为<code>(对象,slice的参数)</code></p>
<p>类似的写法:</p>
<pre><code class="language-js">var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = ;
push(a, 4)
a //

pop(a)
a //
</code></pre>
<p>更进一步<code>bind</code>的调用也可以改写:在<code>Function.prototype.bind</code>上调用<code>call</code>方法(返回的是一个新方法),方法参数是<code>(this对象,bind方法参数)</code>。即最终结果是在<code>this对象</code>上执行<code>bind</code>方法并传递参数。(有些绕)</p>
<pre><code class="language-js">function f() {
console.log(this.v);
}

var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123
</code></pre>
<h1 id="对象的继承">对象的继承</h1>
<ol>
<li>对象的继承可以实现代码的复用</li>
<li>传统JavaScript的继承是通过"原型对象"(prototype)实现的。即js的原型链继承。<em>ES6引入了class语法,实现基于class的继承</em></li>
<li>构造函数的缺点:构造函数中通过给<code>this</code>对象的属性赋值,可以很方便地定义实例对象属性。但是这种方式,同一个构造函数的多个实例之间无法共享属性。</li>
</ol>
<pre><code class="language-js">function Cat(name, color) {
this.name = name;
this.color = color;
this.features = {
    species:'猫',
    habits:'肉食夜行动物'
};
this.meow = function () {
    console.log('喵喵');
};
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow   // false
cat1.features === cat2.features   // false
</code></pre>
<p><code>cat1</code>和<code>cat2</code>是同一个构造函数的两个实例,因为所有<code>meow</code>方法和<code>features</code>对所有实例具有同样的行为和属性,应该共享而不是每个实例都创建新的方法和属性,没必要又浪费系统资源。</p>
<p><strong>原型对象(<code>prototype</code>)用来在实例间共享属性。</strong></p>
<ol start="4">
<li>JavaScript继承机制的设计思想:原型对象的所有属性和方法,都能被实例对象共享</li>
<li>JavaScript规定,<strong>每个函数都有一个<code>prototype</code>属性,指向一个对象</strong>。</li>
</ol>
<pre><code class="language-js">function f() {}
typeof f.prototype // "object"
</code></pre>
<p>普通函数基本不会用<code>prototype</code>属性</p>
<p>构造函数生成实例的时候,构造函数的<code>prototype</code>属性会自动成为实例对象的原型。</p>
<pre><code class="language-js">function Cat(name, color) {
this.name = name;
}
Cat.prototype.color = 'white';
Cat.prototype.features = {
    species:'猫',
    habits:'肉食夜行动物'
};
Cat.prototype.meow = function () {
    console.log('喵喵');
};
var cat1 = new Cat('大毛');
var cat2 = new Cat('二毛');
</code></pre>
<p>原型对象的属性不是实例对象自身的属性。其变动体现在所有实例对象上。</p>
<p>当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。如果实例对象自身就有某个属性或方法,则不会再去原型对象寻找这个属性或方法。</p>
<p>原型对象的作用,是定义所有实例对象共享的属性和方法。这也是被称为原型对象的原因。实例对象可以视作从原型对象衍生出来的子对象。</p>
<ol start="6">
<li>
<p>JavaScript规定,所有对象都有自己的原型对象(<code>prototype</code>)。任何一个对象,都可以充当其他对象的原型;而由于原型对象也是对象,所以它也有自己的原型。这就形成一个"原型链"(<code>prototype chain</code>):对象到原型,再到原型的原型...</p>
</li>
<li>
<p>所有对象的原型最终都可以上溯到<code>Object.prototype</code>,即<code>Object</code>构造函数的<code>prototype</code>属性。所有对象都继承了<code>Object.prototype</code>的属性。</p>
</li>
</ol>
<p>比如所有对象都有<code>valueOf</code>和<code>toString</code>方法,就是从<code>Object.prototype</code>继承的</p>
<p>而<code>Object.prototype</code>对象的原型是<code>null</code>。原型链的尽头是<code>null</code></p>
<p><code>null</code>没有任何属性和方法,也没有自己的原型</p>
<pre><code class="language-js">Object.getPrototypeOf(Object.prototype)// null
</code></pre>
<ol start="8">
<li>
<p>如果对象自身和它的原型,都定义了一个同名属性,则优先读取对象自身的属性,这叫做"覆盖"(<code>overriding</code>)。</p>
</li>
<li>
<p><code>prototype</code>对象有一个<code>constructor</code>属性,默认指向<code>prototype</code>对象所在的构造函数。</p>
</li>
</ol>
<pre><code class="language-js">function P() {}
P.prototype.constructor === P // true
</code></pre>
<p><code>constructor</code>属性的作用是,可以得知某个实例对象由哪一个构造函数产生。另外,有了<code>constructor</code>属性就可以从一个实例对象新建另一个实例。</p>
<pre><code class="language-js">function Constr() {}
var x = new Constr();

var y = new x.constructor();
y instanceof Constr // true
</code></pre>
<p>借助<code>constructor</code>可以在实例方法中调用自身的构造函数</p>
<pre><code class="language-js">Constr.prototype.createCopy = function () {
return new this.constructor();
};
</code></pre>
<ol start="10">
<li><code>constructor</code>属性表明了原型对象与构造函数之间的关联关系。因此如果修改原型对象,一般需要同时修改<code>constructor</code>属性</li>
</ol>
<pre><code class="language-js">function Person(name) {
this.name = name;
}

Person.prototype.constructor === Person // true

Person.prototype = {
method: function () {}
};

Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
</code></pre>
<p><strong>修改原型对象时,一般要同时修改<code>constructor</code>属性的指向</strong></p>
<pre><code class="language-js">// 坏的写法
C.prototype = {
method1: function (...) { ... },
// ...
};

// 好的写法
C.prototype = {
constructor: C,
method1: function (...) { ... },
// ...
};

// 更好的写法
C.prototype.method1 = function (...) { ... };
</code></pre>
<ol start="11">
<li>
<p><code>constructor</code>属性的<code>name</code>属性返回构造函数的名称。</p>
</li>
<li>
<p><code>instanceof</code>表示对象是否为某个构造函数的实例。<code>instanceof</code>做判断时会检查右边构造函数的原型对象(<code>prototype</code>)是否在左边实例对象的原型链上。</p>
</li>
</ol>
<pre><code class="language-js">v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
</code></pre>
<p><code>instanceof</code>会检查整个原型链,因此使用<code>instanceof</code>判断时,实例对象的原型链上可能返回多个构造函数的原型对象</p>
<pre><code class="language-js">var d = new Date();
d instanceof Date // true
d instanceof Object // true
</code></pre>
<p>任意对象(除了<code>null</code>)都是<code>Object</code>的实例。</p>
<pre><code class="language-js">var nullObj=null;
typeof nullObj === 'object' &amp;&amp; !(nullObj instanceof Object);// true
</code></pre>
<p>如果一个对象的原型是<code>null</code>,<code>instanceof</code>的判断就会失真。</p>
<p>利用<code>instanceof</code>可以解决调用构造函数时忘了加<code>new</code>的问题</p>
<ol start="13">
<li>构造函数的继承</li>
</ol>
<p><strong>子类整体继承父类</strong></p>
<p>一、在子类的构造函数中调用父类的构造函数</p>
<pre><code class="language-js">function Sub(value) {
Super.call(this); // 继承父类实例的属性
this.prop = value;
}

// 或者使用另一种写法
function Sub() {
this.base = Super;
this.base();
}
</code></pre>
<p>二、让子类的原型指向父类的原型,继承父类原型</p>
<pre><code class="language-js">Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';
</code></pre>
<p>使用<code>Object.create(Super.prototype)</code>赋值给子类的原型,防止引用赋值,后面的修改影响父类的原型。</p>
<p>上面是比较正确或严谨的写法。比较粗略的写法是直接将一个父类实例赋值给子类的原型</p>
<pre><code class="language-js">Sub.prototype = new Super();
</code></pre>
<p>这种方式在子类中会继承父类实例的方法(通常可能不需要具有父类的实例方法),不推荐</p>
<p><strong>子类中继承父类的单个方法</strong></p>
<pre><code class="language-js">ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// self code
}
</code></pre>
<ol start="14">
<li><strong>多重继承</strong>:JavaScript不提供多重继承功能,即不允许一个对象同时继承多个对象。</li>
</ol>
<p>但是可以通过合并两个父类的原型的形式,间接变通的实现多重继承</p>
<pre><code class="language-js">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'
</code></pre>
<p>这种子类<code>S</code>同时继承了父类<code>M1</code>和<code>M2</code>的模式又称为 <code>Mixin</code>(<code>混入</code>)</p>
<ol start="15">
<li><code>JavaScript</code>不是一种模块化编程语言,<code>ES6</code>才开始支持"类"和"模块"。但是可以利用对象实现模块的效果</li>
<li>模块是实现特定功能的一组属性和方法的封装。所以模块的实现最简单的方式就是把模块写成一个对象,所有模块成员都位于对象里面</li>
</ol>
<ul>
<li><strong>把模块写成一个对象</strong></li>
</ul>
<pre><code class="language-js">var module1 = new Object({
 _count : 0,
 m1 : function (){
  //...
 },
 m2 : function (){
 //...
 }
});
</code></pre>
<p>函数<code>m1</code>、<code>m2</code>和属性<code>_count</code>都封装在<code>module1</code>对象中。使用中直接调用这个对象的属性即可。</p>
<p>但是,这种写法暴露了所有的模块成员,内部状态可以被外部改写。比如,在外部直接改写内部<code>_count</code>的值:<code>module1._count = 5;</code></p>
<ul>
<li><strong>使用构造函数封装私有变量</strong></li>
</ul>
<p>如下,通过构造函数封装实例的私有变量</p>
<pre><code class="language-js">function StringBuilder() {
var buffer = [];

this.add = function (str) {
   buffer.push(str);
};

this.toString = function () {
    return buffer.join('');
};
}
</code></pre>
<p>如下,私有变量<code>buffer</code>在实例对象中,外部是无法直接访问的。</p>
<p>但是,这种方法将私有变量封装在构造函数中,构造函数会和实例对象一直存在于内存中,无法在使用完成后清除。即构造函数的作用既用来生成实例对象,又用来保存实例对象的数据,违背了<strong>构造函数与实例对象在数据上相分离的原则(即实例对象的数据,不应该保存在实例对象以外)</strong>。同时占用内存。</p>
<ul>
<li><strong>构造函数中将私有变量设置为实例属性</strong></li>
</ul>
<pre><code class="language-js">function StringBuilder() {
this._buffer = [];
}

StringBuilder.prototype = {
constructor: StringBuilder,
add: function (str) {
    this._buffer.push(str);
},
toString: function () {
    return this._buffer.join('');
}
};
</code></pre>
<p>这样私有变量就放在了实例对象中。但是私有变量仍然可以从外部读写</p>
<ul>
<li><strong>通过立即执行函数封装私有变量</strong></li>
</ul>
<p>通过"立即执行函数"(<code>Immediately-Invoked Function Expression</code>,<code>IIFE</code>),通过返回"闭包"的方法和属性,实现将属性和方法封装在一个函数作用域里面,函数内的属性作为私有成员不被暴露。</p>
<p>这就是js模块的基本写法:</p>
<pre><code class="language-js">var module1 = (function () {
 var _count = 0;
 var m1 = function () {
 //...
 };
 var m2 = function () {
  //...
 };
 return {
  m1 : m1,
  m2 : m2
 };
})();
</code></pre>
<ul>
<li><strong>模块的放大模式</strong><br>
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时可以采用"放大模式"(<code>augmentation</code>)。</li>
</ul>
<p>如下,为模块<code>module1</code>添加新方法,并返回新的<code>module1</code>模块</p>
<pre><code class="language-js">var module1 = (function (mod){
 mod.m3 = function () {
  //...
 };
 return mod;
})(module1);
</code></pre>
<ul>
<li><strong>"宽放大模式"(<code>Loose augmentation</code>)</strong></li>
</ul>
<p>在立即执行函数的参数中添加空对象,防止加载一个不存在的对象,从而报错或出意外</p>
<pre><code class="language-js">var module1 = (function (mod) {
 //...
 return mod;
})(window.module1 || {});
</code></pre>
<ul>
<li><strong>全局变量的输入</strong></li>
</ul>
<p>模块最重要的是"独立性"。因此为了在模块内部调用(使用)全局变量,必须<strong>显式地将其他变量输入模块内。</strong></p>
<p>比如,下面<code>module1</code>用到了jQuery库(模块),则可以将其作为参数输入<code>module1</code>。保证模块的独立性,并且表明模块之间的依赖关系</p>
<pre><code class="language-js">var module1 = (function ($) {
 //...
})(jQuery);
</code></pre>
<p>立即执行函数还可以起到类似命名空间的作用</p>
<h1 id="object对象的方法"><code>Object</code>对象的方法</h1>
<ol>
<li><code>Object.getPrototypeOf</code>方法返回参数对象的原型。这是获取原型对象的标准方法。</li>
</ol>
<p>几种特殊的原型:</p>
<pre><code class="language-js">// 空对象的原型是 Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true

// Object.prototype 的原型是 null
Object.getPrototypeOf(Object.prototype) === null // true

// 函数的原型是 Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true
</code></pre>
<ol start="2">
<li><code>Object.setPrototypeOf</code>方法为参数对象设置原型,返回该参数对象。<code>Object.setPrototypeOf(obj,prototypeObj)</code></li>
</ol>
<p><code>new</code>命令可以使用<code>Object.setPrototypeOf</code>方法模拟。</p>
<pre><code class="language-js">var F = function () {
this.foo = 'bar';
};
var f = new F();

// 等同于
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
</code></pre>
<ol start="3">
<li><code>Object.create</code>方法以一个对象为原型,返回一个实例对象。该实例完全继承原型对象的属性。</li>
</ol>
<pre><code class="language-js">// 原型对象
var A = {
print: function () {
    console.log('hello');
}
};

// 实例对象
var B = Object.create(A);

Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true
</code></pre>
<p><code>Object.create</code>方法的实现可以用下面的代码代替</p>
<pre><code class="language-js">if (typeof Object.create !== 'function') {
Object.create = function (obj) {
    function F() {}
    F.prototype = obj;
    return new F();
};
}
</code></pre>
<p>生成新的空对象,如下四种是等价的</p>
<pre><code class="language-js">var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();
var obj4 = {};
</code></pre>
<p><code>Object.create</code>的参数为<code>null</code>可以生成一个不继承任何属性(没有<code>toString</code>和<code>valueOf</code>方法)的对象</p>
<pre><code class="language-js">var obj = Object.create(null);
</code></pre>
<p><code>Object.create</code>方法必须指定参数且为对象,否则报错。<code>Object.create</code>创建的对象的原型是引用赋值,即动态继承原型。</p>
<p><code>Object.create</code>方法还可以接受的第二个参数是属性描述对象,描述的对象属性会添加到实例对象的自身属性上。</p>
<pre><code class="language-js">var obj = Object.create({}, {
p1: {
    value: 123,
    enumerable: true,
    configurable: true,
    writable: true,
},
p2: {
    value: 'abc',
    enumerable: true,
    configurable: true,
    writable: true,
}
});

// 等同于
var obj = Object.create({});
obj.p1 = 123;
obj.p2 = 'abc';
</code></pre>
<p><code>Object.create</code>方法生成的对象会继承它的原型对象的构造函数。</p>
<ol start="4">
<li><code>Object.prototype.isPrototypeOf()</code>:实例对象的<code>isPrototypeOf</code>方法判断该对象是否为参数对象原型链上的原型。</li>
</ol>
<p><code>Object.prototype</code>位于除了直接继承自null的对象之外的所有对象的原型链上。</p>
<pre><code class="language-js">Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
</code></pre>
<ol start="5">
<li>关于<code>__proto__</code>属性。<code>__proto__</code>属性是实例对象的属性,表示实例对象的原型(可读写)。实例对象(或非函数对象)无法通过<code>prototype</code>属性获取原型(只有参数才有<code>prototype</code>属性),而<code>__proto__</code>属性默认应该是私有属性,不应该被读写,并且<code>__proto__</code>属性只有浏览器才需要部署。因此,对原型的读写操作正确做法是使用<code>Object.getPrototypeOf()</code>和<code>Object.setPrototypeOf()</code></li>
</ol>
<p>Obj可以用<code>__proto__</code>直接设置原型</p>
<ol start="6">
<li>关于<code>__proto__</code>和<code>prototype</code>属性</li>
</ol>
<p>如下,为构造函数、实例对象、普通对象中__proto__和prototype的对比</p>
<pre><code class="language-js">/** 构造函数的__proto__和prototype **/
var P=function(){}

P.prototype
// {constructor: ƒ}

P.__proto__
// ƒ () { }

P.__proto__===P.prototype
// false

P.__proto__===P.constructor.prototype
// true

P.__proto__===Object.getPrototypeOf(P)
// true

P.__proto__===Function.prototype
// true

P.constructor===Function
// true

/** 实例对象的__proto__和prototype**/
var p=new P()

p.prototype
// undefined
p.__proto__
// {constructor: ƒ}
p.__proto__===Object.getPrototypeOf(p)
// true

p.__proto__===P
// false
p.__proto__===P.prototype
// true

p.constructor===P
// true

/** 实例对象的__proto__和prototype **/
var obj={}

obj.prototype
// undefined

obj.__proto__===Object.getPrototypeOf(obj)
// true

obj.__proto__===Object.prototype
// true

obj.constructor===Object
// true

var nullObj=Object.create(null)

nullObj.__proto__
// undefined
nullObj
// {}无属性
</code></pre>
<p>几点总结:</p>
<ul>
<li>
<p>js中,对象的原型通过<code>__proto__</code>属性获取,由此组成原型链及原型链的继承。</p>
</li>
<li>
<p><code>__proto__</code>是对象自带的属性,除了<code>null</code>和原型对象为<code>null</code>的对象之外,所有的对象都有<code>__proto__</code>属性。函数是对象,因此函数也有<code>__proto__</code>属性</p>
</li>
<li>
<p><code>prototype</code>属性是函数独有的属性,每个函数都有一个<code>prototype</code>属性对象,作用是在实例对象间共享属性和方法。因此<code>prototype</code>只会在构造函数中使用,表示实例对象的原型对象。面向对象中的继承由此实现。</p>
</li>
<li>
<p><code>__proto__</code>属性指向当前对象的原型对象,即构造函数的<code>prototype</code>属性。</p>
</li>
<li>
<p><code>constructor</code>属性表示当前对象的构造函数</p>
</li>
<li>
<p>函数也是对象,因此也拥有<code>__proto__</code>属性,指向当前函数的构造函数的<code>prototype</code>属性。一个函数的<code>constructor</code>是<code>Function</code>,<code>__proto__</code>是<code>Function.prototype</code></p>
</li>
</ul>
<ol start="7">
<li><code>__proto__</code>属性指向当前对象的原型对象,即构造函数的<code>prototype</code>属性。</li>
</ol>
<pre><code class="language-JS">var obj = new Object();

obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true
</code></pre>
<ol start="8">
<li>获取一个对象<code>obj</code>的原型对象,有三种办法:</li>
</ol>
<ul>
<li><code>obj.__proto__</code></li>
<li><code>obj.constructor.prototype</code></li>
<li><code>Object.getPrototypeOf(obj)</code></li>
</ul>
<p>但是 <strong><code>__proto__</code>属性只有浏览器环境才需要部署。<code>obj.constructor.prototype</code>在手动改变原型对象时,可能会失效</strong></p>
<p>如下,将构造函数<code>C</code>的原型对象改为<code>p</code>后。实例对象<code>c.constructor.prototype</code>却没有指向<code>p</code>。<code>Object.getPrototypeOf(obj)</code>正确获取原型对象,是获取原型对象推荐使用的方法</p>
<pre><code class="language-js">var P = function () {};
var p = new P();

var C = function () {};
C.prototype = p;
var c = new C();

c.constructor.prototype === p // false

c.constructor.prototype === P.prototype   // true

Object.getPrototypeOf(c) === p// true
</code></pre>
<p>上面变更原型对象的方法是不正确的。通常<strong>修改<code>prototype</code>时,要同时设置<code>constructor</code>属性。</strong></p>
<pre><code class="language-js">C.prototype = p;
C.prototype.constructor = C;

var c = new C();
c.constructor.prototype === p // true
</code></pre>
<ol start="9">
<li>
<p><code>Object.getOwnPropertyNames()</code>返回对象自身所有属性的键名组成的数组(包括可遍历和不可遍历的所有属性)。</p>
</li>
<li>
<p><code>Object.keys</code>返回对象自身所有可遍历的属性名组成的数组</p>
</li>
<li>
<p><code>Object.prototype.hasOwnProperty()</code>返回一个属性是否为对象自身的属性</p>
</li>
</ol>
<p><em>hasOwnProperty方法是 JavaScript 之中唯一一个处理对象属性时,不会遍历原型链的方法</em></p>
<ol start="12">
<li><code>in</code>运算符表示一个对象是否具有某个属性。即检查一个属性是否存在。</li>
</ol>
<pre><code class="language-js">'length' in Date // true
'toString' in Date // true
</code></pre>
<p><code>for...in</code>循环可以获取一个对象所有可遍历的属性(自身和继承的属性)</p>
<p>通常使用如下方式,遍历对象自身的属性</p>
<pre><code class="language-js">for ( var name in object ) {
if ( object.hasOwnProperty(name) ) {
    /* loop code */
}
}
</code></pre>
<ol start="13">
<li>获取一个对象的所有属性(包含自身的和继承的,以及可枚举和不可枚举的所有属性)</li>
</ol>
<pre><code class="language-js">function inheritedPropertyNames(obj) {
var props = {};
while(obj) {
    Object.getOwnPropertyNames(obj).forEach(function(p) {
      props = true;
    });
    obj = Object.getPrototypeOf(obj);
}
return Object.getOwnPropertyNames(props);
}
</code></pre>
<ol start="14">
<li>对象的拷贝</li>
</ol>
<p>要拷贝一个对象,需要做到下面两点:</p>
<ul>
<li>确保拷贝后的对象,与原对象具有同样的原型。</li>
<li>确保拷贝后的对象,与原对象具有同样的实例属性。</li>
</ul>
<p>如下,为对象拷贝的实现:</p>
<pre><code class="language-js">function copyObject(orig) {
var copy = Object.create(Object.getPrototypeOf(orig));
copyOwnPropertiesFrom(copy, orig);
return copy;
}

function copyOwnPropertiesFrom(target, source) {
Object
    .getOwnPropertyNames(source)
    .forEach(function (propKey) {
      var desc = Object.getOwnPropertyDescriptor(source, propKey);
      Object.defineProperty(target, propKey, desc);
    });
return target;
}
</code></pre>
<p>利用<code>ES2017</code>引入的<code>Object.getOwnPropertyDescriptors</code>可以更简便的实现</p>
<pre><code class="language-js">function copyObject(orig) {
return Object.create(
    Object.getPrototypeOf(orig),
    Object.getOwnPropertyDescriptors(orig)
);
}
</code></pre>
<h1 id="严格模式strict-mode">严格模式(<code>strict mode</code>)</h1>
<ol>
<li>JavaScript提供代码执行的第二种模式:严格模式。严格模式从ES5引入,主要目的为:</li>
</ol>
<ul>
<li>明确禁止一些不合理、不严谨的语法,减少 JavaScript 语言的一些怪异行为。</li>
<li>增加更多报错的场合,消除代码运行的一些不安全之处,保证代码运行的安全。</li>
<li>提高编译器效率,增加运行速度。</li>
<li>为未来新版本的 JavaScript 语法做好铺垫。</li>
</ul>
<ol start="2">
<li>严格模式的启用:在代码头部添加一行<code>'use strict';</code>即可。老版本的引擎会把它当作一行普通字符串,加以忽略。新版本的引擎就会进入严格模式。</li>
<li><code>use strict</code>放在脚本文件的第一行,整个脚本都将以严格模式运行。不在第一行则无效。</li>
<li><code>use strict</code>放在函数体的第一行,则整个函数以严格模式运行。</li>
<li>有时需要把不同脚本文件合并到一个文件。这时,如果一个是严格模式另一个不是,则合并后结果将会是不正确的。解决办法是可以把整个脚本文件放在一个立即执行的匿名函数中:</li>
</ol>
<pre><code class="language-js">(function () {
'use strict';
// some code here
})();
</code></pre>
<ol start="6">
<li>严格模式下的显式报错</li>
</ol>
<p>严格模式下js的语法更加严格,许多在正常模式下不会报错的错误代码都会显式的报错</p>
<p>如下几项操作严格模式下都会报错:</p>
<ul>
<li>
<p>只读属性不可写;比如字符串的<code>length</code>属性</p>
</li>
<li>
<p>不可配置属性无法删除(<code>non-configurable</code>)</p>
</li>
<li>
<p>只设置了取值器的属性不可写</p>
</li>
<li>
<p>禁止扩展的对象不可扩展</p>
</li>
<li>
<p><code>eval</code>、<code>arguments</code> 不可用作标识名</p>
</li>
</ul>
<p>正常模式下,如果函数有多个重名的参数,可以用<code>arguments</code>读取。严格模式下属于语法错误。</p>
<ul>
<li>
<p>函数不能有重名的参数</p>
</li>
<li>
<p>禁止八进制的前缀<code>0</code>表示。<em>八进制使用数字0和字母O表示</em></p>
</li>
</ul>
<ol start="7">
<li>严格模式下的安全限制</li>
</ol>
<ul>
<li>全局变量显式声明</li>
<li>禁止<code>this</code>关键字指向全局对象。避免无意中创造全局变量</li>
</ul>
<pre><code class="language-js">// 正常模式
function f() {
console.log(this === window);
}
f() // true

// 严格模式
function f() {
'use strict';
console.log(this === undefined);
}
f() // true
</code></pre>
<p>严格模式下,函数直接调用时,内部的<code>this</code>表示<code>undefined</code>(未定义),因此可以用<code>call</code>、<code>apply</code>和<code>bind</code>方法,将任意值绑定在<code>this</code>上面。正常模式下,<code>this</code>指向全局对象,如果绑定的值是非对象,将被自动转为对象再绑定上去,而<code>null</code>和<code>undefined</code>这两个无法转成对象的值,将被忽略。</p>
<ul>
<li>
<p>函数内部禁止使用 <code>fn.callee</code>、<code>fn.caller</code></p>
</li>
<li>
<p>禁止使用<code>arguments.callee</code>、<code>arguments.caller</code></p>
</li>
</ul>
<p><code>arguments.callee</code>和<code>arguments.caller</code>是两个历史遗留的变量,从来没有标准化过,现在已经取消</p>
<ul>
<li>禁止删除变量。严格模式下使用<code>delete</code>命令删除一个变量,会报错。只有对象的属性,且属性的描述对象的<code>configurable</code>属性设置为<code>true</code>,才能被<code>delete</code>命令删除。</li>
</ul>
<ol start="8">
<li>静态绑定</li>
</ol>
<ul>
<li>
<p>禁止使用<code>with</code>语句</p>
</li>
<li>
<p>创设<code>eval</code>作用域</p>
</li>
</ul>
<p>正常模式下,<code>JavaScript</code>语言有两种变量作用域(<code>scope</code>):全局作用域和函数作用域。严格模式创设了第三种作用域:<code>eval</code>作用域。</p>
<p><code>eval</code>所生成的变量只能用于<code>eval</code>内部。</p>
<pre><code class="language-js">(function () {
'use strict';
var x = 2;
console.log(eval('var x = 5; x')) // 5
console.log(x) // 2
})()
</code></pre>
<p><code>eval</code>语句使用严格模式:</p>
<pre><code class="language-js">// 方式一
function f1(str){
'use strict';
return eval(str);
}
f1('undeclared_variable = 1'); // 报错

// 方式二
function f2(str){
return eval(str);
}
f2('"use strict";undeclared_variable = 1')// 报错
</code></pre>
<ul>
<li><code>arguments</code>不再追踪参数的变化。严格模式下参数修改,<code>arguments</code>不再联动跟着改变</li>
</ul>
<ol start="9">
<li>面向<code>ECMAScript 6</code></li>
</ol>
<ul>
<li>ES5的严格模式只允许在全局作用域或函数作用域声明函数。</li>
<li>保留字。严格模式新增了一些保留字:<code>implements</code>、<code>interface</code>、<code>let</code>、<code>package</code>、<code>private</code>、<code>protected</code>、<code>public</code>、<code>static</code>、<code>yield</code>等</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    <div style="display:inline-block;width:80%">
<div>
作者:
代码迷途
</div>
<div>
出处:
https://www.cnblogs.com/codemissing/
</div>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意原创文章必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。<br>非原创文章若有需要,建议直接联系原文作者或保留声明情况下转载原文
</div></div>
<img src="https://blog-static.cnblogs.com/files/codemissing/onlyqr_codemissing.gif" style="width:19%;margin-bottom:-13px;margin-top: -7px;min-width:110px;"><br><br>
来源:https://www.cnblogs.com/codemissing/p/JavaScript_Object_Oriented_OOP.html
頁: [1]
查看完整版本: 《JavaScript语言入门教程》记录整理:面向对象