复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和undefined)。
null、空数组和空对象都是合法的 JSON 值。
字符串必须使用双引号表示,不能使用单引号。
对象的键名必须放在双引号里面。
数组或对象最后一个成员的后面,不能加逗号。
JSON.stringify()和JSON.parse()。JSON.parse()方法还原。)'foo' 会被转化为 "\"foo\"",这是为了后续还原时也能正确转为字符串。(外层可以是双引号,也可以是单引号的)JSON.stringify()方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。
JSON.stringify()还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。‘\t’ (分行显示)、1-10数字(表示属性前添加的空格)(0添加也没啥用。大于10就还是10个空格)toJSON()方法,那么JSON.stringify()会使用这个方法的返回值作为参数,而忽略原对象的其他属性。(就是有toJSON方法时,只看它了)toJSON()方法的一个应用是,将正则对象自动转为字符串。

JSON.parse()方法用于将 JSON 字符串转换成对应的值。JSON.parse()方法放在try...catch代码块中。JSON.parse()方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify()方法类似。作用:执行构造函数,返回一个实例对象
使用new命令时,根据需要,构造函数也可以接受参数。
new命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。但为了表示这里是函数调用,推荐使用括号。
如果忘了使用new命令,直接调用构造函数,会发生什么事?
这时,构造函数就变成了普通函数,并不会生成实例对象。this这时代表全局对象,将造成一些意想不到的结果。
为了保证构造函数必须与new命令一起使用:
use strict。这样的话,一旦忘了使用new命令,直接调用构造函数就会报错。
创建一个空对象,作为将要返回的对象实例。
将这个空对象的原型,指向构造函数的prototype属性。
将这个空对象赋值给函数内部的this关键字。
开始执行构造函数内部的代码。
new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。this关键字是一个非常重要的语法点。value里。 var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
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
~~好神奇有趣~
call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。call方法还可以接受多个参数。func.call(thisValue, arg1, arg2, ...)
call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。
call方法的一个应用是调用对象的原生方法。
Object.prototype.xx方法.call(对象名, 参数)apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。func.apply(thisValue, [arg1, arg2, ...])
apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。
第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。
有趣的应用
1.找出数组最大元素
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
2.将数组的空元素变为undefined
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。
所以使用apply的话,可以让数组完整遍历。
3.转换类似数组的对象
length属性,以及相对应的数字键。Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({length: 1}) // [undefined]
4.绑定回调函数的对象
jQuery相关吧。
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
一个比较好理解的,手动确定this执行环境的例子。

注意这个5是x参数呀,第二次调用时接收的是y参数。因为第一个参数是对象。后面的才是函数的参数。

null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)。JavaScript 继承机制的设计思想就是,prototype的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在prototype上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
(好像有点理解了,之前的就是会在prototype上定义一些公共的属性和方法啥的)
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
因为,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
(所以其实也挺好的,改一个,全部能应用上)
首先有一个构造函数,然后呢,构造函数有些原型对象,这时候实例化,就可以用原型对象了。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
// 好的写法:将constructor属性重新指向原来的构造函数。再修改。
C.prototype = {
constructor: C,
method1: function (...) { ... },
// ...
};
// 更好的写法:只在原型上添加方法,不做其他的改动。
C.prototype.method1 = function (...) { ... };
因为其实如果按“好的写法”而不加重新指向的话,相当于重新定义了一次了。那肯定就有问题哦。
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
~~ 从翻译来看就是: x 是不是 X 的实例呢?是;否~~
X是不是x的原型呢?是;否
由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true。
由于任意对象(除了null)都是Object的实例,所以instanceof运算符可以判断一个值是否为非null的对象。
instanceof运算符的一个用处,是判断值的类型。(仅限于对象,字符串(原始类型)不是对象(也不是String对象的实例),所以不行啦)
利用instanceof运算符可以解决:调用构造函数时,忘了加new命令的问题。
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
} else {
return new Fubar(foo, bar);
}
}
instanceof运算符,在函数体内部判断this关键字是否为构造函数Fubar的实例。如果不是,就表明忘了加new命令。
-1、完整继承的方法:
让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。第一步是在子类的构造函数中,调用父类的构造函数。
function Sub(value) {
Super.call(this);
this.prop = value;
}
上面代码中,Sub是子类的构造函数,this是子类的实例。在实例上调用父类的构造函数Super,就会让子类实例具有父类实例的属性。
(Super.call(this)就是调用啊?call可以绑定this莫)
第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';
上面代码中,要将Sub.prototype赋值为Object.create(Super.prototype),而不是直接等于Super.prototype。否则后面两行对Sub.prototype的操作,会连父类的原型Super.prototype一起修改掉。(涉及到内部的this的指向?
另一种将Sub.prototype = 一个父类实例的写法就达咩。子类会有父类实例的方法,这有时不是我们需要的。所以不推荐。
ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// some code
}
上面代码中,子类B的print方法先调用父类A的print方法,再部署自己的代码。这就等于继承了父类A的print方法。
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'
这种模式又称为:Mixin
var module1 = (function () {
var _count = 0;
var m1 = function () {
//...
};
var m2 = function () {
//...
};
return {
m1 : m1,
m2 : m2
};
})();
使用上面的写法,外部代码无法读取内部的_count变量。
console.info(module1._count); //undefined
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
例子:
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);
把这两个库(其实是两个模块)当作参数输入module1。
这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
(很多之前提过的)
var F = function () {
this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
上面代码中,new命令新建实例对象,其实可以分成两步。
第一步,将一个空对象的原型设为构造函数的prototype属性(上例是F.prototype);第二步,将构造函数内部的this绑定这个空对象,然后执行构造函数,使得定义在this上面的方法和属性(上例是this.foo),都转移到这个空对象上。
生成新对象的三种方式 完全等价
var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();
Object.getPrototypeOf()和Object.setPrototypeOf() 差不多的。获取实例对象obj的原型对象,有三种方法。
obj.__proto__
obj.constructor.prototype
Object.getPrototypeOf(obj)
上面三种方法之中,前两种都不是很可靠。
__proto__属性只有浏览器才需要部署,其他环境可以不部署。
而obj.constructor.prototype在手动改变原型对象时,可能会失效。(就是用这个方法的话改变原型对象,还要同时把这个属性给重新定义一下,不然是不会生效的,前面也有提到过)
所以第三种是推荐的。
Object.getOwnPropertyNames方法返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名。(不管是否可以遍历)
Object.keys方法。in运算符返回一个布尔值,表示一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性。function inheritedPropertyNames(obj) {
var props = {};
while(obj) {
Object.getOwnPropertyNames(obj).forEach(function(p) {
props[p] = true;
});
obj = Object.getPrototypeOf(obj);
}
return Object.getOwnPropertyNames(props);
}
上面代码依次获取obj对象的每一级原型对象“自身”的属性,从而获取obj对象的“所有”属性,不管是否可遍历。(到了最后一级就是Object,也会被遍历到)
确保拷贝后的对象,与原对象具有同样的原型。
确保拷贝后的对象,与原对象具有同样的实例属性。
例子有点点复杂= =
另一种更简单的写法,是利用 ES2017 才引入标准的
Object.getOwnPropertyDescriptors方法。
function copyObject(orig) {
return Object.create(
Object.getPrototypeOf(orig),
Object.getOwnPropertyDescriptors(orig)
);
}
// 正常模式
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
JavaScript 语言的一个特点,就是允许“动态绑定”,即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。
严格模式对动态绑定做了一些限制。某些情况下,只允许静态绑定。也就是说,属性和方法到底归属哪个对象,必须在编译阶段就确定。这样做有利于编译效率的提高,也使得代码更容易阅读,更少出现意外。