Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
說起JavaScript,大家都知道是一門腳本語言。那么ES是什么鬼呢?ES全稱ECMAScript ,是JavaScript語言的國(guó)際標(biāo)準(zhǔn)。
最近,我總結(jié)了25條JavaScript的基礎(chǔ)特性相關(guān)的知識(shí)點(diǎn),大家一起看一下吧
function funValue(value) { value="dada"; alert(value); // dada alert(argument[0]); // 非嚴(yán)格模式:dada // 嚴(yán)格模式模式 dadaqianduan}?funValue('dadaqianduan');
在JavaScript當(dāng)中如何聲明一個(gè)類?如何定義類中的方法?如何實(shí)例化對(duì)象?
我們來看看下面的代碼示例:
// es5?let dada=function(type) { this.type=type}?dada.prototype.study=function() { console.log('魔王哪吒');}?let da1=new dada('程序員')let da2=new dada('It')?da1.constructor.prototype.study=function() { console.log('dadaqianduan');}da1.study()
JavaScript constructor 屬性
定義和用法
constructor 屬性返回對(duì)創(chuàng)建此對(duì)象的數(shù)組函數(shù)的引用。
語法
object.constructor
constructor 是一種用于創(chuàng)建和初始化class創(chuàng)建的對(duì)象的特殊方法。
// es6class Da { constructor(name) { // 構(gòu)造函數(shù)內(nèi)寫屬性 this.name=name; } eat() { // 構(gòu)造函數(shù)外寫方法 console.log('i eat') }}?const da1=new Da('da1');?console.log(da1.name); // da1console.log(da1);
var daObj={ get val() { return ; }, set val(value) { }}
get:
var da={ a: 1, get val(){ return this.a + 1; }}?console.log(da.val);//2da.val=100;console.log(da.val);//2?class Da { constructor(type) { this.type=type } get age() { return 1 } set age(val) { this.realAge=val } eat() { console.log('i am eat') }}let da1=new Da('da1')console.log(da1.age)da1.age=1console.log(da1.realAge)
class Da { constructor(type, age) { this.type=type this.age1=age } get age() { return this._age } set age(val) { this._age=val }}
利用set/get實(shí)現(xiàn)對(duì)element.innerHTML封裝
class myHTMLElement { constructor(element) { this.element=element } get html() { return this.element.innerHTML } set html(value) { this.element.innerHTML=value }}
設(shè)置一個(gè)閉包,通過一定的規(guī)則來限制對(duì)它的修改:
let myName='dada'class Da { constructor(type) { this.type=type } get name() { return myName } set name(val) { myName=val }}
在es5中實(shí)現(xiàn)的靜態(tài)方法:
let Da=function (type) { this.type=type this.eat=function() { console.log('i eat') }}Da.study=function(book) { console.log('i book');}
let Da=function(type) { this.type=type}Da.prototype.eat=function() { Da.walk() console.log('i am')}Da.walk=function(){ console.log('walk')}let da1=new Da('da1')da1.eat()?// walk// i am
靜態(tài)方法在你的實(shí)例化對(duì)象是找不到的
在es6中的靜態(tài)方法,標(biāo)記static
class Da { constructor(type) { this.type=type } eat() { console.log('i eat') } static study() { console.log('i study') }}
在es5中的繼承:
// 定義一個(gè)父類let Da=function(type) { this.type=type}// 定義方法Da.prototype.eat=function() { console.log('i am')}// 定義靜態(tài)方法Da.study=function(book) { console.log('i study')}// 定義子類let Da1=function() { // 初始化父類 Da.call(this, 'da1'); this.run=function() { console.log('i run') }}// 繼承Da1.prototype=Da.prototype
在es6中的繼承
class Da { constructor(type) { this.type=type } eat() { // Da.walk(); console.log('i eat') } static walk(){ console.log('i walk') }}?class da extends Da { // 構(gòu)造函數(shù) //constructor (type) { //super(type) //} run() { console.log('i run') }}let da1=new da('da1')
類的聲明,屬性,方法,靜態(tài)方法,繼承,多態(tài),私有屬性
// 類的聲明let Da=function(type) { this.type=type this.eat=function() { console.log('i eat'); }}?let da=new Da('da');
// prototypelet Da=function(type) { this.type=type}Da.prototype.eat=function() { console.log('i eat')}let da1=new Da('da1')
es6中的Class
class Da { // 構(gòu)造函數(shù) constructor(type) { this.type=type } // 方法 walk() { console.log('i walk') }}let da=new Da('da');// console.log(typeof Da); function
函數(shù)參數(shù)是從左到右解析,如果沒有默認(rèn)值會(huì)被解析成 undefined
// 參數(shù)默認(rèn)值function da (x,y,z) {}function sum() { let num=0 Array.prototype.forEach.call(arguments, function(item){ num +=item * 1 }) Array.from(arguments).forEach(function(item){ num +=item * 1 }) return num}
// 不確定function sum(...nums) { let num=0 nums.forEach(function(item){ num +=item * 1 }) return num}console.log(sum(1,2,3,4,5))
function sum () { let num=0 Array.prototype.forEach.call(arguments, function (item) { num +=item * 1 }) return num}?function sum (...nums) { let num=0 nums.forEach(function (item) { num +=item * 1 }) return num}
箭頭函數(shù)表達(dá)式的語法比函數(shù)表達(dá)式更簡(jiǎn)潔,并且沒有自己的this,arguments,super或new.target。箭頭函數(shù)表達(dá)式更適用于那些本來需要匿名函數(shù)的地方,并且它不能用作構(gòu)造函數(shù)。
()=> {}// function Da() {}// let da=function() {}let da=()=> { console.log('hello')}da()?let da=name=> {}
const materials=[ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium'];console.log(materials.map(material=> material.length));// expected output: Array [8, 6, 7, 9]
拓展
判斷函數(shù)有幾個(gè)參數(shù)
console.log(sum(...[4]))console.log(sum.apply(null, [4]))
JavaScript當(dāng)中,函數(shù)的參數(shù)前面有三個(gè)點(diǎn),代表什么呢?我們看下代碼示例:
function myFunc(a, b, ...args) { console.log(a); // 22 console.log(b); // 98 console.log(args); // [43, 3, 26]};myFunc(22, 98, 43, 3, 26);
function myFunc(x, y, ...params) { // used rest operator here console.log(x); console.log(y); console.log(params);}?var inputs=["a", "b", "c", "d", "e", "f"];myFunc(...inputs); // used spread operator here// "a"// "b"// ["c", "d", "e", "f"]
var obj1={ foo: 'bar', x: 42 };var obj2={ foo: 'baz', y: 13 };?var clonedObj={ ...obj1 };// Object { foo: "bar", x: 42 }?var mergedObj={ ...obj1, ...obj2 };// Object { foo: "baz", x: 42, y: 13 }
JS中對(duì)象的屬性定義,代碼示例如下:
let x='da1';let y='da2';let obj={ x, y}console.log(obj);?// 結(jié)果{x:'da1',y:'da2'}
let x=1; let y=2; let z=3let obj={ 'x': x, y, [z+y]: 4, * hello() { // 異步 console.log('dada') }}// function* functionName() {}obj.hello()
Set存儲(chǔ)的成員不允許的重復(fù)的(它類似于數(shù)組)
Set 本身是一個(gè)構(gòu)造函數(shù),用來生成 Set 數(shù)據(jù)結(jié)構(gòu)。
const s=new Set();?[2, 3, 5].forEach(x=> s.add(x));?Set 函數(shù)可以接受一個(gè)數(shù)組(或類似數(shù)組的對(duì)象)作為參數(shù),用來初始化?const set=new Set([1, 2, 3, 4, 4]);
實(shí)現(xiàn)數(shù)組去重
var arr=[1,1,2,2,3,3]; // step1:數(shù)組轉(zhuǎn)集合var s=new Set(arr); // 已經(jīng)去掉重復(fù)值,當(dāng)前不是數(shù)組,而集合s.size; // 3// step2:集合轉(zhuǎn)數(shù)組console.log([...s]); // 1,2,3;?// Array.form 方法可以將 Set 結(jié)構(gòu)轉(zhuǎn)為數(shù)組const items=new Set([1, 2, 3]);const arr=Array.from(items);?function dada(array) { return Array.from(new Set(array));}?dada([1, 1, 2])
Set的遍歷
操作方法
let set=new Set([1, 2, 3, 4, 4]);?// 添加數(shù)據(jù) let addSet=set.add(5);console.log(addSet); // Set(5) {1, 2, 3, 4, 5}?// 刪除數(shù)據(jù) let delSet=set.delete(4);console.log(delSet); // true?// 查看是否存在數(shù)據(jù) 4let hasSet=set.has(4);console.log(hasSet); // false?// 清除所有數(shù)據(jù)set.clear();console.log(set); // Set(0) {}
實(shí)現(xiàn)并集(Union)、交集(Intersect)和差集(Difference)
let a=new Set([1, 2, 3]);let b=new Set([4, 3, 2, 1]);?// 并集let union=new Set([...a, ...b]);// Set {1, 2, 3, 4}?// 交集let intersect=new Set([...a].filter(x=> b.has(x)));// set {1, 2, 3}?// 差集let difference=new Set([...b].filter(x=> !a.has(x)));// Set {4}
JS當(dāng)中的哈希表,使用方法如下:
let map=new Map()map.set(1, 2)map.set(3, 4)map.set(1, 3)console.log(map)?創(chuàng)建var da=new Map();var jeskson={};遍歷da.forEach(function(value,key,map){}長(zhǎng)度da.size刪除//da.delete() 刪除key,全部清楚da.clear()新增da.set(key,value)da.has(查索引值)?da.forEach((value,key)=>{})?for( let [key, value] of map){}?// let map=new Map( [[1,2], [3,4]] )?map的key任意都可以let o=function() { console.log('o')}map.set(o, 3)console.log(map.get(o)); // 3
// map.jsvar Dictionary=function() { var items={}; // 檢查鍵 this.has=function(key) { return key in items; } // 添加鍵值對(duì) this.set=function(key, value){ items[key]=value; } // 通過鍵移除元素 this.delete=function(key) { if(this.has(key)){ delete items[key] return true } return false } // 鍵獲取值 this.get=function(key){ return this.has(key) ? items[key] : undefined; } // 列表返回字典值 this.values=function() { var values=[]; for(var k in items) { if(this.has(k)) { values.push(items[k]) } } return values; } // 獲取全部鍵名 this.keys=function() { return Object.keys(items); } // 獲取私有變量items this.getItems=function() { return items; }}
Map數(shù)據(jù)結(jié)構(gòu),它類似于對(duì)象,也是鍵值對(duì)的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對(duì)象)都可以當(dāng)作鍵。
Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象。
const target={ a: 1, b: 2 };const source={ b: 4, c: 5 };?const returnedTarget=Object.assign(target, source);?console.log(target);// expected output: Object { a: 1, b: 4, c: 5 }?console.log(returnedTarget);// expected output: Object { a: 1, b: 4, c: 5 }?> Object { a: 1, b: 4, c: 5 }> Object { a: 1, b: 4, c: 5 }
語法
Object.assign(target, ...sources)
參數(shù)
target
目標(biāo)對(duì)象
sources
源對(duì)象
返回值
目標(biāo)對(duì)象。
const obj={ a: 1 };const copy=Object.assign({}, obj);console.log(copy); // { a: 1 }
Object.assign(undefined) // 報(bào)錯(cuò)Object.assign(null) // 報(bào)錯(cuò)?let obj={a: 1};Object.assign(obj, undefined)===obj // trueObject.assign(obj, null)===obj // true?const obj1={a: {b: 1}};const obj2=Object.assign({}, obj1);?obj1.a.b=2;obj2.a.b // 2?const target={ a: { b: 'c', d: 'e' } }const source={ a: { b: 'hello' } }Object.assign(target, source)// { a: { b: 'hello' } }?const source={ get foo() { return 1 }};const target={};?Object.assign(target, source)// { foo: 1 }
Object.assign復(fù)制的是屬性值value,如果屬性值是一個(gè)引用類型,那么復(fù)制的其實(shí)是引用地址,就會(huì)存在引用共享的問題(Object.assign(target,source1,...,sourceN)淺拷貝的過程)
要點(diǎn):
function ObjectAssign(target, ...sources) { // 對(duì)第一個(gè)參數(shù)的判斷,不能為undefined和null if(target===undefined || target===null) { throw my TypeError('error'); } // 將第一個(gè)參數(shù)轉(zhuǎn)為對(duì)象(不是對(duì)象轉(zhuǎn)換為對(duì)象) const targetObj=Object(target); // 將源對(duì)象自身的所有可枚舉屬性復(fù)制到目標(biāo)對(duì)象 for(let i=0; i<sources.length; i++){ let source=sources[i]; // 對(duì)于undefined和null在源中不會(huì)報(bào)錯(cuò),會(huì)直接跳過 if(source !==undefined && source !==null) { // 將源象轉(zhuǎn)換成對(duì)象 // 需要將源自身的可枚舉數(shù)據(jù)進(jìn)行復(fù)制 // Reflect.ownKeys(obj) const keysArray=Reflect.ownKeys(Object(source)); for (let nextIndex=0; nextIndex < keysArray.length; nextIndex++) { const nextKey=keysArray[nextIndex]; // 去除不可枚舉屬性 const desc=Object.getOwnPropertyDescriptor(source,nextKey); if(desc!==undefined&&desc.enumerable){ targetObj[nextKey]=source[nextKey]; } } } } return targetObj;}if(typeof Object.myAssign !=='function'){ Object.defineProperty(Object, 'myAssign', { value: ObjectAssign, writable: true, enumerable: false, configurable: true });}
淺拷貝 Object.assign 的實(shí)現(xiàn)原理
拷貝第一層的基本類似值和第一層的引用類型地址:
let da1={ name: 'da1', age: 1}?let da2={ name: 'da2', study: { title: 'web' }}?let da3=Object.assign(da1,da2);console.log(da3);// {// name: 'da2',// age: 1,// study: { title: 'web' }// }console.log( da1===da3); // true?da2.name='da22';da2.study.title='web2';console.log(da2);// {// name: 'da22',// study: { title: 'web2' }// }?console.log(da1);// {// age: 1,// name: 'da2',// study: { title: 'web2' }// }
如果源對(duì)象的屬性值是一個(gè)指向?qū)ο蟮囊茫仓豢截愡@個(gè)引用地址哦!
let da1={ name: 'da1', age: 1}let da2={ a: Symbol('dadaqianduan'), b: null, c: undefined}let da3=Object.assign(da1, da2);console.log(da3);// {// name: 'da1',// age: 1,// a: Symbol('dadaqianduan'),// b: null,// c: undefined// }console.log(da1===da3); // true
let map=new Map([iterable])// Map是用來實(shí)現(xiàn)字典的功能-Object鍵值對(duì)
動(dòng)態(tài)屬性鍵
// ES5 codevar key1='one', obj={ two: 2, three: 3 };?obj[key1]=1;?// obj.one=1, obj.two=2, obj.three=3?// ES6 codeconst key1='one', obj={ [key1]: 1, two: 2, three: 3 };?// obj.one=1, obj.two=2, obj.three=3?// ES6 codeconst i=1, obj={ ['i' + i]: i };?console.log(obj.i1); // 1
補(bǔ)充:前端面試考點(diǎn),HTML和CSS,性能優(yōu)化,原型,作用域,異步,各種手寫代碼,DOM事件和Ajax,HTTP協(xié)議。
模板文字是es2015/es6的新功能,與es5及以下版本相比,可以通過新穎的方式使用字符串,先只需要反引號(hào)代替單引號(hào)或雙引號(hào)即可:
const module_string=`dadaqianduan`
它們之所以獨(dú)特是因?yàn)樗鼈兲峁┝撕芏嘤靡?hào)構(gòu)建的普通字符串不具備的功能:
使用多行字符串
在es6之前的版本:
// 要?jiǎng)?chuàng)建跨越兩行的字符串,必須\在行尾使用字符?const dada='dada \ dadaqianduan' // 呈現(xiàn)效果:在兩行上創(chuàng)建一個(gè)字符串,但是僅在一行上呈現(xiàn)
要在多行上呈現(xiàn),則需要使用\n在每行的末尾添加
const string='dada 魔王哪吒\n \ dadaqianduan'
使用反引號(hào)打開模板文字后,只需要按enter鍵就行:
const dada=`dadaqianduan 魔王哪吒`
在這里請(qǐng)記住空間是有意義的:
const da=`First Second`
使用trim()方法,可以消除第一個(gè)字符之前的任何空格
插補(bǔ):模板文字提供了一種將變量和表達(dá)式插入字符串的簡(jiǎn)便的方法
const da=`dadaqianduan ${mydada}`?${}里面可以添加任何東西?const da1=`dada ${1+2+3}`const da2=`dada ${dafun() ? 'x' : 'y'}`
let da=['hello', 'world']let [firstName, surName]=dacosole.log(firstName, surName);
解構(gòu)賦值在于賦值,拷貝出來賦值給變量,而賦值的元素本身不會(huì)發(fā)生改變
默認(rèn)值
let [da1, da2]=[];?console.log(da1); // undefinedconsole.log(da2); // undefined
給變量賦值(默認(rèn)值),防止出現(xiàn)undefined的情況:
let [da1='da1', da2='da2']=['dadaqianduan]?console.log(da1); // dadaqianduanconsole..log(da2); // da2
解構(gòu)分配
ES5中的索引提取這些值:
var myArray=['a', 'b', 'c'];var one=myArray[0], two=myArray[1], three=myArray[2];?// one='a', two='b', three='c'
ES6解構(gòu)允許使用更簡(jiǎn)單方法:
const [one, , three]=myArray;?// one='a', three='c'
使用rest運(yùn)算符(...)提取剩余元素:
const [one, ...two]=myArray;?// one='a', two=['b, 'c']
const myObject={ one: 'a', two: 'b', three: 'c'};?// ES6 destructuring exampleconst {one: first, two: second, three: third}=myObject;?// first='a', second='b', third='c'
可變值交換
var a=1, b=2;?// ES5 swapvar temp=a;a=b;b=temp;?// a=2, b=1?// ES6 swap back[a, b]=[b, a];?// a=1, b=2?[b, c, d, e, a]=[a, b, c, d, e];
在ES6中,我們可以為任何參數(shù)分配默認(rèn)值
function dada(param={}) {
函數(shù)返回多個(gè)值(函數(shù)只能返回一個(gè)值,但可以是一個(gè)復(fù)雜的對(duì)象或多維數(shù)組)
function f() { return [1, 2, 3];}?const [a, b, c]=f();?// a=1, b=2, c=3
ES6 JavaScript深度解構(gòu)
默認(rèn)情況下,找不到的屬性為undefined
var {da}={bar: 'dada'}console.log(da)// undefined
如果訪問不存在的父級(jí)的深層嵌套屬性,則將獲得異常。
var {da:{bar}}={dada: 'dadaqianduan'}// Exception
var key='dadaqianduan'var { [key]: foo }={ dadaqianduan: 'bar' }console.log(foo)// 'bar'
var {da=3}={ da: 2 }console.log(da)// 2var {da=3}={ da: undefined }console.log(da)// 3var {da=3}={ bar: 2 }console.log(da)// 3?var [a]=[]console.log(a)// undefinedvar [b=10]=[undefined]console.log(b)// 10var [c=10]=[]console.log(c)// 10?function da () { return { x: 1, y: 2 }}var {x, y}=da()console.log(x)// 1console.log(y)// 2
Callback
Promise
function loadScript(src) { return new Promise((resolve, reject)=> { let script=document.createElement('script') script.src=src script.onload=()=> resolve(src) script.onerror=(err)=> reject(err) document.head.append(script) })}
function loadScript(src) { let script=document.createElement('script'); script.src=src; document.head.append(script)}
var promise=new Promise(function(resolve, reject){ resolve('傳遞給then的值')})promise.then(function(value){ console.log(value)},function(error){ console.error(error)})
Promise對(duì)象是用于表示一個(gè)異步操作的最終完成(或失敗),以及其結(jié)果值。
示例:
const promise=new Promise((resolve, reject)=> { setTimeout(()=> { resolve('da'); }, 200);});?promise.then((value)=> { console.log(value);});?console.log(promise);
語法:
new Promise(function (resolve,reject){...});
描述:Promise對(duì)象是一個(gè)代理對(duì)象,被代理的值在Promise對(duì)象創(chuàng)建時(shí)可能是未知的,它允許你為異步操作的成功和失敗分別綁定相應(yīng)的處理方法,這讓異步方法可以像同步方法那樣返回值,但并不是立即返回最終執(zhí)行結(jié)果,而是一個(gè)能代表未來出現(xiàn)的結(jié)果的promise對(duì)象。
一個(gè)Promise有以下幾種狀態(tài):
pending狀態(tài)的Promise對(duì)象可能會(huì)變?yōu)閒ulfilled狀態(tài)并傳遞一個(gè)值給相應(yīng)的狀態(tài)處理方法。
Promise.prototype.then和Promise.prototype.catch方法返回promise對(duì)象,所以它們可以被鏈?zhǔn)秸{(diào)用。
方法:
Promise.all(iterable)
Promise.any(iterable)
Promise.reject(reason)
Promise.resolve(value)
Promise原型
屬性:Promise.prototype.constructor返回被創(chuàng)建的實(shí)例函數(shù),默認(rèn)為Promise函數(shù)。
方法:
function myAsyncFunction(url) { return new Promise((resolve, reject)=> { const xhr=new XMLHttpRequest(); xhr.open('GET',url); xhr.onload=()=> resolve(xhr.responseText); xhr.onerror=()=> reject(xhr.statusText); xhr.send(); });}
示例:
var target={}var handler={}var proxy=new Proxy(target, handler)proxy.a='b'console.log(target.a)// 'b'console.log(proxy.c===undefined)// true
為了更好地了解代理的有用性,讓我們做一些小練習(xí)。
示例:
想象一下,您已經(jīng)17歲了,即將滿18歲。并且您希望您的程序在打開時(shí)自動(dòng)向您祝賀。為此,您可以使用代理。
var person={ name: "dada", age: 17};?person=new Proxy(person, { set(target, property, value) { if (value===18) { console.log("Congratulations! You are of legal age"); Reflect.set(target, property, value); return true; } }});?person.age=18;?if (value < 13 && value > 99) { throw new Error('The age should be between 13 and 99')} else { Reflect.set(target, property, value)}
語法:
let p=new Proxy(target, handler)
如果不想再調(diào)用key的時(shí)候,返回undefined:
console.log(o.dada || '')
使用Proxy
let o={ name:'dada', age: 1}?let handler={ get(obj, key) { return Reflect.has(obj, key)?obj[key]:'' }}?let p=new Proxy(o, handler)?console.log(p.from)
希望從服務(wù)器獲取的數(shù)據(jù)只讀,不允許修改:
for (let [key] of Object.entries(response.data)) { Object.defineProperty(response.data, key, { writable: false })}
使用Proxy:
let data=new Proxy(response.data, { set(obj, key, value) { return false }})
檢驗(yàn)邏輯代碼:
// Validator.js?export default(obj, key, vlaue)=> { if(Reflect.has(key) && value > 20) { obj[key]=value }}?import Validator from './Validator'?let data=new Proxy(response.data, { set: Validator})
使用Proxy,對(duì)讀寫進(jìn)行監(jiān)控:
let validator={ set(target, key, value) { if(key==='age') { if(typeof value !=='number' || Number.isNaN(value)) { throw new TypeError('Age must be a number') } if(value<=0){ throw new TypeError('Age must be a positive number') } } return true }}?const person={ age: 12 }const proxy=new Proxy(person,validator)proxy.age='dada' // TypeError numberproxy.age=NaNproxy.age=0 // positive numberproxy.age=3
示例:每個(gè)對(duì)象都有一個(gè)自己的id
class Component { constructor() { this.proxy=new Proxy({ id: Math.random().toString(36).slice(-8) }) } get id() { return this.proxy.id }}
function * dada() { for(let i=0; i<2; i++ { yield console.log(i); }}?const da=dada()da.next()da.next()
Generator函數(shù)與普通函數(shù)的區(qū)別在于定義的時(shí)候有一個(gè)*,執(zhí)行下面函數(shù):
function* dada() {console.log('dadaqianduan');}dada(); // 沒有執(zhí)行函數(shù) 如需要輸出,改為:var da=dada();da.next();
要生成一個(gè)自增的id:
var count_id=0;function dada_id() {count_id ++;return count_id;}
方法
Generator.prototype.next()返回一個(gè)由 yield表達(dá)式生成的值。?Generator.prototype.return()返回給定的值并結(jié)束生成器。?Generator.prototype.throw()向生成器拋出一個(gè)錯(cuò)誤。
書寫風(fēng)格:
function *da() {}?function* da(){}
方法
Generator對(duì)象方法:next,return,throw
通過Next方法來獲取每一次遍歷的結(jié)果,這個(gè)方法返回一個(gè)對(duì)象,這個(gè)對(duì)象包含兩個(gè)value和done。
value:當(dāng)前程序的運(yùn)行結(jié)果 done:遍歷是否結(jié)束
next是可以接收參數(shù)的,這個(gè)參數(shù)可以讓你在generator外部給內(nèi)部傳遞數(shù)據(jù),這個(gè)參數(shù)就是作為yield的返回值。
return()方法可以讓generator遍歷終止
function * da() { yield 1 yield 2 yield 3}var d=da()console.log(d.next()) // {value:1,done:false}console.log(d.return()) // {value:undefined,done:true}console.log(d.next()) // {value:undefined,done:true}
return可以傳入?yún)?shù),作為返回的value的值
function * da() { yield 1 yield 2 yield 3}var d=da()console.log(d.nex()) // {value:1,done:false}console.log(d.return(100)) // {value:100,done:true}console.log(d.next()) // {value:undefined,done:true}
throw()方法在generator外部控制內(nèi)部執(zhí)行的“終斷”
generator函數(shù)聲明:
function* genFunc(){...}const genObj=genFunc();
generator表達(dá)式:
const genFunc=function* () {...}const genObj=genFunc();
對(duì)象中定義:
const obj={ * generatorMethod(){ ... }}const genObj=obj.generatorMethod();
類定義(類聲明或類表達(dá)式):
class MyClass{ * generatorMethod(){ ... }}const myInst=new MyClass();const genObj=myInst.generatorMethod();
最簡(jiǎn)單的iterator遍歷規(guī)范:
authors[Symbol.iterator]=function(){ // this return { next(){ return{ done:false, value:1 } } }}
在es6前,js文件之間的導(dǎo)入,導(dǎo)出是借助require.js,sea.js,如現(xiàn)在使用import,export,來實(shí)現(xiàn)原生javascript的導(dǎo)入,導(dǎo)出。
export:
導(dǎo)出變量或者常量?export const da='dadaqianduan'export let da1='da1'export var da2='da1'?const name='dada'let name1='dada1'export { name, name1}?導(dǎo)出函數(shù)export function da(value){ console.log(value)}?const da=(value)=> { console.log(value);}?export { da}?導(dǎo)出Objectexport({ name: 'da1', message: 'dadaqianduan'})?let da={ name: 'name1'}export { da}?導(dǎo)出Classclass Da { constructor(){ this.id=1 }}export { Da}?export class Da { constructor() { this.id=1 }}?修改導(dǎo)出名稱const name='da1'export { name as cname}export default name
import
// 直接導(dǎo)入const name='dada'let name1='dada1'var name2='dada2'export { name as cname}export default name2?import name2, {name1, name} from A
export const sqrt=Math.sqrt;export function square(x) { return x * x;}export function dada(x,y) { return sqrt(square(x) + square(y));}?import {square,da} from 'da';console.log(square(11)); // 121console.log();
export default function() {...}import myFunc from 'myFunc';export default class{...}import MyClass from 'MyClass';const inst=new MyClass();
require--lib.js--function add(x,y){ return x + y}module.exports={ add: add,};?--main.js--var add=require('lib').addd;console.log(add(1,2));
import--lib.js--export function add(x,y) { return x + y}--main.js--import {add} from 'lib';console.log(add(1,2));
--lib.js--export const sqrt=Math.sqrt;export function square(x) { return x * x;}export function da(x,y) { return sqrt(square(x)+square(y));}--main.js--import {square, da} from 'lib'??--myFunc.js--export default function() {...};--main.js--import myFunc from 'myFunc';myFunc();
該方法判斷一個(gè)數(shù)組是否包含一個(gè)指定的值,返回布爾值
let da1=[1,2,3];console.log(da1.includes(2));
arr.find(function(item){return item===1;})?arr.filter(function(item){return item===2;})?Math.pow(2,3)->2**3
async function firstAsync(){ let promise=new Promise ((resolve,reject)=> { setTimeout(function(){ resolve('dadaqianduan') },1000) }) console.log(await promise) console.log(await Promise.resolve(1)) console.log(2) return Promise.resolve(3)}firstAsync().then(val=> { console.log(val)})
await后面是Promise對(duì)象
Object.values()返回一個(gè)數(shù)組,其元素是在對(duì)象上找到的可枚舉屬性值。
let da={ 'da': 1, 'da2': 2}console.log(Object.value(da)) // [1,2]?Object.values是在對(duì)象上找到可枚舉的屬性的值,所以只要這個(gè)對(duì)象是可枚舉的就可以
Object.entries()方法返回一個(gè)給定對(duì)象自身可枚舉屬性的鍵值對(duì)數(shù)組
題目一:
Promise.resolve().then(()=>{ console.log(1)}).catch(()=>{ console.log(2)}).then(()=>{ console.log(3)})
題目二:
Promise.resolve().then(()=>{ console.log(1) throw new Error('da')}).catch(()=>{ console.log(2)}).then(()=>{ console.log(3)})
題目三:
Promise.resolve().then(()=>{ console.log(1) throw new Error('da')}).catch(()=>{ console.log(2)}).catch(()=>{ console.log(3)})
題目四:
async function fn() { return 1}(async function() { const a=fn() // ?? const b=await fn() // ??})()
題目五:
console.log(100)setTimeout( ()=> { console.log(200)})Promise.resolve().then( ()=> { console.log(300)})console.log(400)
題目六:
async function async1() { console.log('async1 start') await async2() console.log('async1 end')}?async function async2 () { console.log('async2')}?console.log('script start')?setTimeout(function(){ console.log('setTimeout')},0)?async1()?new Promise(function (resolve){ console.log('promise1') resolve()}).then(function(){ console.log('promise2')})?console.log('script end')
加載圖片:
// 加載function loadImg(src) { const p=new Promise( (resolve,reject)=> { const img=document.createElement('img') img.onload=()=>{ resolve(img) } img.onerror=()=>{ const err=new Error('圖片加載失敗') reject(err) } img.src=src } ) return p}const url='https'const p=loadImg(url)?p.then(img=>{ console.log(img.width) return img}).then(img=>{ console.log(img.height)}).catch(ex=> { console.error(ex)})
async function async1() { console.log('async1 start') // 2 await async2() // undefined console.log('async1 end') // 5}async function async2() { console.log('async2') // 3}console.log('script start') // 1async1()console.log('script end') // 4
for...of常用于異步的遍歷
function add(num) { return new Promise(resolve=> { setTimeout(()=>{ resolve(num*num) },1000) })}const nums=[1,2,3]nums.forEach(async(i)=>{ const res=await add(i)})
宏任務(wù):setTimeout,setInterval,ajax等 微任務(wù):Promise async/await
微任務(wù)執(zhí)行時(shí)比宏任務(wù)要早:
宏任務(wù):DOM渲染后觸發(fā),如setTimeout
微任務(wù):DOM渲染前觸發(fā),如Promise
function da(time) { return new Promise(function(resolve,reject){ setTimeout(function(){ resolve(time) },time) })}async function test() { let arr=[da(2000),da(1000),da(3000)] for await (let item of arr) { console.log(Date.now(), item) }}
const input={ a: 1, b: 2}const output={ ...input, c: 3}console.log(output)?const input={ a: 1, b: 2, c: 3}let {a, ...rest }=input
該方法會(huì)按照一個(gè)可指定的深度遞歸遍歷數(shù)組,并將所有元素與遍歷到的子數(shù)組中的元素合為一個(gè)新數(shù)組。
Array.prototype.flat()建議將數(shù)組遞歸展平至指定范圍depth并返回新數(shù)組。
depth(指定要提取嵌套數(shù)組的結(jié)構(gòu)深度)
語法:Array.prototype.flat(depth)
depth —默認(rèn)值1,Infinity用于展平所有嵌套數(shù)組。
const numbers=[1, 2, [3, 4, [5, 6]]];?// Considers default depth of 1numbers.flat(); ?> [1, 2, 3, 4, [5, 6]]?// With depth of 2numbers.flat(2); ?> [1, 2, 3, 4, 5, 6]?// Executes two flat operationsnumbers.flat().flat(); ?> [1, 2, 3, 4, 5, 6]?// Flattens recursively until the array contains no nested arrays?numbers.flat(Infinity)> [1, 2, 3, 4, 5, 6]
語法:Array.prototype.flatMap(callback)
callback:function產(chǎn)生新Array的元素。
const numbers=[1, 2, 3];?numbers.map(x=> [x * 2]);> [[2], [4], [6]]?numbers.flatMap(x=> [x * 2]);> [2, 4, 6]
Object.fromEntries
Object.fromEntries執(zhí)行與的相反操作Object.entries。它將一組鍵值對(duì)轉(zhuǎn)換為一個(gè)對(duì)象。
const records=[['name','da'], ['age', 32]];?const obj=Object.fromEntries(records);?> { name: 'da', age: 32}?Object.entries(obj);?> [['name','Mathew'], ['age', 32]];
Symbol.prototype.description
只讀屬性,返回Symbol對(duì)象的可選描述:
Symbol('desc').toString();> "Symbol(desc)"?Symbol('desc').description; > "desc"?Symbol('').description; > ""?Symbol().description;> undefined
好了各位,以上就是這篇文章的全部?jī)?nèi)容,能看到這里的人都是人才。我后面會(huì)不斷更新技術(shù)相關(guān)的文章,如果覺得文章對(duì)你有用,歡迎給個(gè)“贊”,也歡迎分享,感謝大家 !!
喜歡本文的朋友,歡迎關(guān)注公眾號(hào) 程序員小灰,收看更多精彩內(nèi)容
日,在世紀(jì)之初風(fēng)靡全球、一時(shí)風(fēng)頭無兩的女星“小甜甜”布蘭妮·斯皮爾斯再次出現(xiàn)在公眾視野。但這一次,不是因?yàn)樗屓f人空巷的全球巡演,不是因?yàn)樗裏徜N3000萬張的個(gè)人專輯,而是因?yàn)樗?qǐng)求法院解除父親杰米·斯皮爾斯對(duì)自己的永久監(jiān)護(hù)權(quán)。
“小甜甜”布蘭妮·斯皮爾斯。
在6月23日的庭審中,布蘭妮通過語音連線,向法官講述了過去13年中,自己遭受父親及其工作團(tuán)隊(duì)的虐待,具體內(nèi)容包括被迫長(zhǎng)時(shí)間、高強(qiáng)度工作,接受非自愿并有較大副作用的精神治療,安置節(jié)育環(huán)以致她無法再次懷孕。此外,處于監(jiān)護(hù)狀態(tài)下的她,完全喪失了對(duì)自己財(cái)產(chǎn)的使用權(quán)利,連簡(jiǎn)單的度假、美甲、按摩等需求都無法得到父親批準(zhǔn),“在加州,只有被關(guān)起來的性工作者才這么慘——信用卡、現(xiàn)金、手機(jī)、護(hù)照全部被收走。”
在通話的最后,布蘭妮說道:“我希望我能一直和你聊下去。我生怕一掛掉電話,就會(huì)立刻回到被人全盤否定的狀態(tài)。我覺得自己被排擠、被欺負(fù)、被冷落……我受夠了這種孤獨(dú)的狀態(tài)。我值得擁有和任何人一樣的權(quán)利,我也想有孩子、有家庭、有自己的人生……”
2008年,布蘭妮因使用精神類藥物而被強(qiáng)制送往精神醫(yī)療中心治療。之后,她的父親杰米向法院申請(qǐng),成為了布蘭妮的“永久監(jiān)護(hù)人(Conservator)”。根據(jù)美國(guó)法律,“監(jiān)護(hù)(Conservatorship)”是一種將個(gè)體的個(gè)人、經(jīng)濟(jì)和法律決策權(quán)轉(zhuǎn)讓給他人的機(jī)制。對(duì)于因年老癡呆、身體或精神缺陷而無法自主作出決定的成年人,法院可以任命一位法定監(jiān)護(hù)人,負(fù)責(zé)監(jiān)督他的日常活動(dòng),如醫(yī)療保健或生活安排,并對(duì)它的財(cái)務(wù)事務(wù)進(jìn)行托管。
此后13年,布蘭妮便一直處于被父親“監(jiān)護(hù)”的狀態(tài)之下。盡管在此期間,她發(fā)行了4張專輯,其中2張成為白金唱片(在美國(guó),專輯銷量超過100萬張即可被認(rèn)證為“白金唱片”),并在2018年進(jìn)行了全球巡回演出,但法院仍判定,“她的精神狀態(tài)無法自主作出明智的決定”。
在社交網(wǎng)絡(luò)上,由布蘭妮粉絲發(fā)起的“釋放布蘭妮(Free Britney)”運(yùn)動(dòng)已進(jìn)行多年。自2020年起,布蘭妮本人也試圖通過法律途徑擺脫被父親監(jiān)護(hù)的狀態(tài)。
而這一次,布蘭妮本人通過連線出庭,聲淚俱下地講述了自己這13年的遭遇。這不僅牽動(dòng)了成千上萬關(guān)注她的粉絲的心,也讓更多粉絲之外的法律研究者、婦女權(quán)利保護(hù)者乃至美國(guó)國(guó)會(huì)議員開始反思“監(jiān)護(hù)人”制度的正當(dāng)性。
同時(shí),布蘭妮的遭遇也引發(fā)了不少好萊塢工作者的共情。在他們看來,是娛樂圈長(zhǎng)期盛行的厭女風(fēng)氣,加之狗仔娛記和大眾審視的步步緊逼,共同造成了布蘭妮的悲劇。
為什么身為成年人、具有基本工作能力的布蘭妮還要被“監(jiān)護(hù)”?是什么讓她被判定為“精神失常”,被媒體和輿論塑造為一個(gè)“瘋女人”?誰該為布蘭妮的悲劇負(fù)責(zé)?本文系統(tǒng)梳理了國(guó)內(nèi)外多家媒體以及相關(guān)領(lǐng)域?qū)<业母鞣接^點(diǎn)。
撰文 | 肖舒妍
備受爭(zhēng)議的監(jiān)護(hù)權(quán):是保護(hù)還是虐待?
“如果我能工作、賺錢、還能付錢給別人,我就不應(yīng)該被監(jiān)護(hù),”在6月23日的庭審中,布蘭妮提出了這一觀點(diǎn),“這毫無意義。法律需要改變。”
最初,布蘭妮是如何“陷入”監(jiān)護(hù)之中的?
2007年,布蘭妮的狀態(tài)一度滑入低谷。飽受輿論壓力的她先是在一家理發(fā)店公開剃光了自己的頭發(fā),對(duì)著鏡頭大喊“我受夠了別人不停碰我!”緊接著又陷入了和前夫凱文·費(fèi)德勒爭(zhēng)奪兩個(gè)孩子撫養(yǎng)權(quán)的官司。她失去了孩子的獨(dú)立撫養(yǎng)權(quán),僅獲得探視權(quán)。在一次探望孩子卻被前夫拒之門外之后,面對(duì)狗仔的長(zhǎng)槍短炮和不斷追問,布蘭妮情緒崩潰,舉起一把雨傘企圖趕走狗仔,卻被狗仔拍下了她失控的狀態(tài),放上雜志封面。一時(shí)間,布蘭妮“陷入瘋癲”、成為“瘋女人”的傳聞便不絕于耳。
2007年2月,布蘭妮情緒失控之后,在理發(fā)店公開剃光了自己的頭發(fā)。
次年一月的一個(gè)晚上,費(fèi)德勒來到布蘭妮家中,準(zhǔn)備接走兩個(gè)孩子,卻發(fā)現(xiàn)布蘭妮把自己和兒子鎖在衛(wèi)生間,不肯開門。警方趕到后,發(fā)現(xiàn)布蘭妮“受到精神類藥物影響”,于是將其強(qiáng)制送往精神醫(yī)療中心治療。在她住院期間,杰米·斯皮爾斯向法院提交申請(qǐng),獲得了女兒的“臨時(shí)監(jiān)護(hù)權(quán)”。在2008年10月,“臨時(shí)監(jiān)護(hù)權(quán)”又轉(zhuǎn)為“永久監(jiān)護(hù)權(quán)”。
盡管此后布蘭妮的精神狀態(tài)有所好轉(zhuǎn),監(jiān)護(hù)托管卻并未因此終止。熟悉此案的律師Vivian Thoreen在《紐約時(shí)報(bào)》拍攝的紀(jì)錄片《陷害布蘭妮》(Framing Britney Spears)中提到,如果被監(jiān)護(hù)人想要終止監(jiān)護(hù),需要向法院提交一份請(qǐng)?jiān)笗⑻峁┳约阂巡恍枰O(jiān)護(hù)(或監(jiān)護(hù)已經(jīng)失效)的證據(jù),但在她所參與的所有案件中,沒有一個(gè)被監(jiān)護(hù)人曾成功終止監(jiān)護(hù),“一旦進(jìn)入監(jiān)護(hù)系統(tǒng),就很難再有可能脫身”。
“如果個(gè)人想要終止監(jiān)護(hù),責(zé)任應(yīng)該由反對(duì)終止的一方承擔(dān)(以證明監(jiān)護(hù)有必要繼續(xù)),但實(shí)際上,通常情況下,往往是個(gè)人必須證明他們不再需要被監(jiān)護(hù)。”卡多佐法學(xué)院(Cardozo School of law)的臨床法學(xué)教授、監(jiān)護(hù)法專家萊斯利·薩爾茲曼(Leslie Salzman)這樣表示。
“就像‘第22條軍規(guī)’的規(guī)定,瘋子可以免于飛行任務(wù),但必須由飛行員本人提出申請(qǐng),而本人一旦提出申請(qǐng),便可證明他并不是瘋子。想從監(jiān)護(hù)中脫身,布蘭妮必須要證明自己有管理生活以及財(cái)產(chǎn)的能力,但她正處于監(jiān)護(hù)中,也就意味著她沒有上述能力。” 支持布蘭妮的導(dǎo)演史塔克向《綜藝》雜志說道。
一個(gè)精神正常的人該如何證明自己精神正常呢?如果布蘭妮在監(jiān)護(hù)狀態(tài)下身心健康,則可以解釋為“監(jiān)護(hù)制度行之有效”;如果布蘭妮對(duì)監(jiān)護(hù)狀態(tài)掙扎抵抗,又可以說明“她無法做出理智判斷,必須接受監(jiān)護(hù)”;在監(jiān)護(hù)狀態(tài)下,布蘭妮犯的任何一個(gè)小錯(cuò)誤,都能夠作為她不宜解除監(jiān)護(hù)的證據(jù)。但即使一個(gè)身心健全的普通人,也并不總能做出符合個(gè)人最大利益的明智決定。
“如果一個(gè)普通人決定,‘我要休假,喝幾杯酒,吃一大堆甜甜圈,然后小睡一會(huì)兒,’這是很正常的事情,屬于個(gè)人基本權(quán)利,”記者Sara Luterman舉例論證,“但如果你有精神疾病或生理缺陷,這就會(huì)被視為你‘沒有能力負(fù)責(zé)任地管理自己的生活’的進(jìn)一步證據(jù)。”
在2020年的庭審中,法官在判決時(shí)提出,布蘭妮的情況很特殊,她是一名“具有高能力的受監(jiān)護(hù)人”。許多人對(duì)此感到疑惑,這顯然是個(gè)自相矛盾的詞。
另一方面,布蘭妮的監(jiān)護(hù)人人選也飽受質(zhì)疑。杰米·斯皮爾斯雖然是布蘭妮的父親,卻在2008年之前長(zhǎng)期缺席她的人生,與妻子離婚多年,甚至找不到一張他和女兒合影。此外,他曾因酗酒被送進(jìn)強(qiáng)制康復(fù)機(jī)構(gòu),多次創(chuàng)業(yè)失敗最終申請(qǐng)破產(chǎn)。在紀(jì)錄片《陷害布蘭妮》中,布蘭妮曾經(jīng)的唱片營(yíng)銷總監(jiān)回憶:“我不能判斷杰米是個(gè)怎樣的人,我只見過他一面。他對(duì)我說過唯一的一句話就是:‘我女兒會(huì)變得很富有,她會(huì)給我買艘大船。’”
布蘭妮的父親杰米·斯皮爾斯。(圖源:紀(jì)錄片《陷害布蘭妮》)
無論是布蘭妮的母親林恩(Lynn),還是自她5歲起就認(rèn)識(shí)她、之后一直陪伴她參加活動(dòng)、幫助她打理事業(yè)的長(zhǎng)期助理Felicia Culotta,都比杰米更加了解、關(guān)心布蘭妮的生活狀態(tài)。在進(jìn)入監(jiān)護(hù)狀態(tài)之后,布蘭妮曾多次向法院提出更換監(jiān)護(hù)人,在2020年更表示,如果杰米繼續(xù)掌控她的事業(yè),她將拒絕演出。
“雖然受監(jiān)護(hù)人未必能夠選擇自己的監(jiān)護(hù)人,但他們當(dāng)然可以推薦并說明希望誰成為監(jiān)護(hù)人。法院應(yīng)該對(duì)這一請(qǐng)求給予充分考慮。考慮到監(jiān)護(hù)人要托管受監(jiān)護(hù)人的個(gè)人事務(wù),他要能夠被對(duì)方所接受。”薩爾茲曼這樣認(rèn)為。而在布蘭妮的案例中,她的需求顯然沒有被尊重。
布蘭妮的支持者要求法院終止她的“被監(jiān)護(hù)”狀態(tài)。
加之布蘭妮作為全球巨星所擁有的高額財(cái)產(chǎn),進(jìn)一步提高了監(jiān)護(hù)權(quán)的復(fù)雜程度。布蘭妮的公開資產(chǎn)為6000萬美元,本身極為可觀,同時(shí)有媒體披露,布蘭妮的實(shí)際財(cái)產(chǎn)超過6億美元,但大部分均被杰米轉(zhuǎn)移。此外,布蘭妮還要提供自己全部收入的1.5%作為“監(jiān)護(hù)傭金”。很難判斷對(duì)杰米而言,女兒是需要照顧的受監(jiān)護(hù)人,還是日進(jìn)斗金的搖錢樹。
布蘭妮在法庭陳述中提到,自己被要求每周工作7天,每天工作10小時(shí),2018年的巡回演出和在拉斯維加斯長(zhǎng)達(dá)4年的駐唱都是被迫簽約,即便在發(fā)燒40度時(shí)她也要上臺(tái)演出。根據(jù)《紐約時(shí)報(bào)》的調(diào)查,布蘭妮在拉斯維加斯的演出總票房高達(dá)1.37億美元,但屬于布蘭妮的分成只有每周2000美元。
而在2020年接受法院?jiǎn)栐儠r(shí),杰米團(tuán)隊(duì)的律師更提出,他們將采用一種“全新的混合型商業(yè)模式”,幫助布蘭妮完成事業(yè)開拓和財(cái)富增長(zhǎng)。有團(tuán)隊(duì)表示,監(jiān)護(hù)團(tuán)隊(duì)成立時(shí),布蘭妮的資產(chǎn)只有幾百萬美元,而現(xiàn)在已超過6000萬美元,這證明了團(tuán)隊(duì)的監(jiān)護(hù)工作行之有效。但他們似乎沒有意識(shí)到,托管成立的初衷,是為了幫助布蘭妮恢復(fù)身心健康,而不是讓她成為賺錢機(jī)器。
在布蘭妮與杰米對(duì)簿公堂時(shí),她不僅要支付自己的治療費(fèi)用、自己的法務(wù)費(fèi)用,還要支付杰米團(tuán)隊(duì)的工資,杰米方的法務(wù)費(fèi)用。
杰奎琳·布徹(Jacqueline Butcher)曾是斯皮爾斯家族的好友,并在2008年提供證詞幫助杰米獲得監(jiān)護(hù)權(quán)。而現(xiàn)在,她對(duì)自己過去的行為表示遺憾,“當(dāng)時(shí)我以為自己是在幫忙,但實(shí)際上我?guī)鸵粋€(gè)腐敗的家族掌控了一切。”
“盡管布蘭妮的情況看起來非常極端并令人不安,” Erica Schwiegershausen在《紐約》雜志中寫道,“她講述的大部分內(nèi)容——例如未經(jīng)同意接受藥物治療、接受非自愿精神病評(píng)估和被強(qiáng)制送進(jìn)精神病院——對(duì)任何有精神疾病經(jīng)歷的人來說都很熟悉。”據(jù)估計(jì),在美國(guó)有數(shù)以百萬計(jì)的智力缺陷或社交能力缺陷者被剝奪了法律行為能力,處于某種形式的監(jiān)護(hù)之下。這導(dǎo)致了一系列虐待行為,包括強(qiáng)制醫(yī)療、強(qiáng)制避孕、強(qiáng)制終止妊娠、非自愿監(jiān)禁、強(qiáng)制生活安排和行動(dòng)自由受限。
紀(jì)錄片《陷害布蘭妮》劇照。
父權(quán)控制與女性反抗:一場(chǎng)漫長(zhǎng)的斗爭(zhēng)
在布蘭妮所遭受的一系列限制和虐待中,“強(qiáng)制避孕”一點(diǎn)尤其令人不解,無論從任何角度都無法看作是對(duì)于布蘭妮的保護(hù)。在法庭陳述中,布蘭妮說道:“我希望能夠結(jié)婚生子,但這卻被監(jiān)護(hù)團(tuán)隊(duì)所禁止。我的體內(nèi)放有宮內(nèi)節(jié)育器,因此無法懷孕。我想取出宮內(nèi)節(jié)育器,再生一個(gè)孩子,可是這個(gè)所謂的團(tuán)隊(duì)不讓我接近醫(yī)生。”
限制布蘭妮生育這一行為,幾乎遭到了美國(guó)各黨派人士的譴責(zé),包括美國(guó)計(jì)劃生育行動(dòng)基金董事會(huì)主席亞歷克西斯·麥吉爾·約翰遜(Alexis McGill Johnson),保守派共和黨參議員特德克魯茲(Ted Cruz)和眾議員南希·梅斯(Nancy Mace)、卡羅琳·馬洛尼(Carolyn B.Maloney)。
“你可以打著保護(hù)的幌子強(qiáng)迫一個(gè)女人絕育,這太瘋狂了。如果這能發(fā)生在布蘭妮·斯皮爾斯身上,全國(guó)還會(huì)有多少其他女人在默默受苦?” 眾議員南希·梅斯在推特上寫道。
“從一開始,男人就控制著女人的身體、思想和抱負(fù),”另一位眾議員卡羅琳·馬洛尼則向《華盛頓郵報(bào)》表示,“斯皮爾斯女士和世界各地的任何女性一樣,理應(yīng)對(duì)自己的身體、權(quán)益和財(cái)產(chǎn)享有完全的自主權(quán)。”
這一控制在布蘭妮身上顯得尤其讓人心碎。在出道伊始,布蘭妮的形象便與獨(dú)立、自主、強(qiáng)大聯(lián)系在一起。在早年的采訪視頻中,她笑著告訴記者:“我知道自己所有的合同,我知道自己所有要做的工作,我才不是那種只聽經(jīng)紀(jì)人話的女孩。”
曾與布蘭妮合作過的伴舞也多次提到:“她絕不是牽線木偶。她是老大(boss)。”對(duì)于演出造型、舞蹈動(dòng)作、演唱歌曲,布蘭妮都會(huì)堅(jiān)定而自信地提出自己的看法。
在粉絲看來,布蘭妮展示出的“女人的我行我素,不必再取悅他人”,正是她最大的魅力所在。“布蘭妮的形象不再只是父權(quán)制下少女形象的意義典范,而是父權(quán)控制與女性抵抗,甚至資本主義與個(gè)體之間的符號(hào)沖突(semiotic struggle)的表現(xiàn)。”公眾號(hào)“看理想”在一篇文章中如此概括布蘭妮的文化意義。
但是當(dāng)時(shí)的美國(guó)大眾,一方面狂熱地喜愛這樣一個(gè)“既性感又純潔、既乖巧又獨(dú)立”的熒幕形象,一方面卻又沒做好準(zhǔn)備接受這樣的女性走出熒幕、走進(jìn)生活。“在‘圣經(jīng)地帶’出生、長(zhǎng)大的布蘭妮,身上集中了美國(guó)人的兩種期望:既要穿著性感,給人曖昧的想象,又要保持純潔的處女身;既要盡可能地觸探性的界限,又絕不能越雷池半步。兩種期望顯然是矛盾的,這讓她受到了嚴(yán)格的形象、行為、道德審查。” 作者李孟蘇在“三聯(lián)生活周刊”公眾號(hào)文章中寫道。
紀(jì)錄片《陷害布蘭妮》截圖。
當(dāng)時(shí)美國(guó)馬里蘭州的州長(zhǎng)夫人肯德爾·埃爾利希(Kendel Ehrlich)甚至公開對(duì)媒體表示:“如果我有機(jī)會(huì),真想一槍崩了布蘭妮。”以此指責(zé)布蘭妮給孩子們做了不良的榜樣,讓美國(guó)的媽媽們感到不安。
在一次訪談中,主持人當(dāng)著布蘭妮的面播放了這段視頻,布蘭妮聽到后緊蹙雙眉,微微搖頭,“太可怕了……可是,我不是用來幫她們教育小孩的啊。”她忍不住哭了出來。
在紀(jì)錄片《陷害布蘭妮》中,一位業(yè)內(nèi)人士提出,沒有一個(gè)男孩樂隊(duì)的成員會(huì)像布蘭妮這樣受到如此嚴(yán)格的道德審查。
“讓我們直截了當(dāng)?shù)卣f:發(fā)生在布蘭妮·斯皮爾斯身上的事情,永遠(yuǎn)不會(huì)發(fā)生在一個(gè)男版的她身上。”專欄作家海琳·奧倫(Helaine Olen)在《華盛頓郵報(bào)》一針見血地寫道,“想想看,有多少男明星曾在公共場(chǎng)合做出過瘋狂的、情緒失控的、甚至磕了藥似的行為。幾秒內(nèi)我就能說出邁克爾·杰克遜、坎耶·韋斯特和小羅伯特·唐尼的名字。小羅伯特·唐尼曾被警察帶走,理由是他光著身子走進(jìn)鄰居家,躺在孩子的床上睡著了。當(dāng)鄰居撥打911時(shí),你甚至可以聽到他的鼾聲。還有布蘭妮的父親,杰米——一個(gè)非常不合時(shí)宜的男人,布蘭妮的前夫曾申請(qǐng)并獲得了限制令,禁止杰米靠近自己以及兩個(gè)孩子。”
“管理學(xué)理論表明,人們會(huì)默認(rèn)男人是有能力的,不管他們的過去如何;而女性卻不得不一次又一次證明自己的工作能力、財(cái)務(wù)敏感、理智水平。” 海琳·奧倫繼續(xù)寫道,“縱觀歷史,監(jiān)護(hù)制度和非自愿承諾制度一直被用來控制女性的人身自由和財(cái)務(wù)自由。”
紀(jì)錄片《陷害布蘭妮》截圖。一位曾經(jīng)合作過的演員在接受采訪時(shí)如此評(píng)論布蘭妮。
回看當(dāng)時(shí)輿論對(duì)布蘭妮的圍觀,以及對(duì)她“精神病”的指控,都彌漫著濃厚的厭女氣息。莫伊拉·多納根(Moira Donegan)在《衛(wèi)報(bào)》寫道:“歷史上,許多人僅僅因?yàn)槌钟胁皇軞g迎的觀點(diǎn),或行為冒犯了普遍傳統(tǒng),就被認(rèn)為是瘋了。女性尤其深受其害。長(zhǎng)期以來,那些厭惡女性或覬覦女性財(cái)富的人,總能因?yàn)橐恍┪⒉蛔愕赖脑颍雅灾缚爻莎傋印!?/p>
而在紀(jì)錄片《陷害布蘭妮》于今年2月5日上線之后,聯(lián)合國(guó)婦女署活動(dòng)家蒙羅·伯格多夫(Munroe Bergdorf)在社交媒體上嚴(yán)肅表態(tài):“布蘭妮為自己所取得的成就付出了太大的代價(jià)。當(dāng)時(shí)的社會(huì)選擇對(duì)精神疾病避而不談,不愿正視女性的自我風(fēng)貌,不知如何消解四處蔓延的厭女情緒。女性公眾人物被媒體拿來消費(fèi)。世人追捧你到制高點(diǎn),為的是最終能親手將你毀滅。”
大眾審視與娛樂產(chǎn)業(yè):誰是施害者,誰是受害者?
“世人追捧你到制高點(diǎn),為的是最終能親手將你毀滅。”這句話,可能道出了布蘭妮悲劇的本質(zhì)。
首張個(gè)人專輯《…Baby One More Time》讓她一夜成名,全球銷量超過3000萬張,成為世界銷量最高的專輯之一。但水能載舟亦能覆舟,隨著喜愛而來的是大眾對(duì)她從頭到腳的審視,以及對(duì)她私生活永不饜足的窺探。
布蘭妮的首張個(gè)人專輯《…Baby One More Time》封面。
在布蘭妮10歲剛出道的一段視頻中,一位滿頭銀發(fā)的主持人問身高才到自己腰身的布蘭妮:“你有男朋友了嗎?……你可以考慮我。”而當(dāng)時(shí)的觀眾,并沒有人感到不適。
年齡稍長(zhǎng)之后,布蘭妮遇到的采訪問題更加刻薄露骨:“你還是處女嗎?”“你知道所有人都在想著你的胸嗎?”而在2001年的一段電視采訪中(當(dāng)時(shí)布蘭妮只有19歲),主持人直言:“對(duì)許多人來說,你是一個(gè)矛盾體。一面是甜美、純潔、童貞,一面是只穿著內(nèi)衣的性感蕩婦。”布蘭妮只好尷尬回應(yīng):“不是‘只穿著內(nèi)衣’,在《滾石》的封面上是唯一一次。我在演出時(shí)不會(huì)。”
攝影師大衛(wèi)·拉查佩爾(David LaChapelle)拍攝的這張著名照片登在了《滾石》的封面上,讓布蘭妮成為被公眾凝視的對(duì)象。
一張布蘭妮的“黑照”,價(jià)值100萬美元,在紀(jì)錄片《陷害布蘭妮》中職業(yè)狗仔記者Daniel Ramos提到。重賞之下,必有勇夫。于是布蘭妮的私人生活也暴露在了長(zhǎng)槍短炮、層層包圍的鏡頭之下。
在前文提及的布蘭妮在前夫家探視孩子未果后,正是Daniel Ramos步步緊逼、追問布蘭妮的心情,最終致使她情緒崩潰,揮舞雨傘砸向了他開來的車。在拍下了布蘭妮的失態(tài)照片后,他摸著車上坑坑洼洼的痕跡,喜笑顏開:“天啊,這次賺大了。”憑借幾次三番偷拍到布蘭妮形容憔悴、情緒失控或是剃成光頭的照片,Daniel Ramos賺得盆滿缽滿。
多年以后,《紐約時(shí)報(bào)》紀(jì)錄片團(tuán)隊(duì)問這名娛樂記者:“你覺得自己影響了布蘭妮的生活嗎?”
Daniel Ramos回答:“我不這么認(rèn)為。在她身邊工作了這么多年,她從來沒對(duì)我們說‘我不想理你們,別煩我了’。”
團(tuán)隊(duì)反問:“她說過‘別煩我’吧?”
Daniel Ramos想了片刻,回答道:“她是說過‘今天你能讓我一個(gè)人待著嗎?’,但并不代表說‘永遠(yuǎn)別煩我’啊。”
紀(jì)錄片《陷害布蘭妮》截圖。直播采訪時(shí),主持人問布蘭妮:“你是不是希望那些狗仔隊(duì)都走開?”
比起布蘭妮巔峰時(shí)期溫暖陽光的笑容,她低谷階段剃光的頭發(fā)、濃重的黑眼圈似乎更能勾起大眾的注意、引發(fā)人們的唏噓。
《每日人物》主筆安小慶曾把布蘭妮的境遇與她筆下香港的“瘋女人們”進(jìn)行類比。在《香港為什么有那么多“瘋女人”? 》一文中,她寫道:
藍(lán)潔瑛頂著“四大癲王“的封號(hào),與后來的“瘋女人“們——吳綺莉、吳卓林、關(guān)淑怡一起,以肉身的磨蝕和精神的苦痛,源源不斷地給香港社會(huì)供應(yīng)著日常運(yùn)轉(zhuǎn)所需的“瘋癲“樣本。比起“傳奇“,他們更熱衷狗血和瘋癲,尤其是那些從原有高階層跌落折墮的“瘋女人“的故事,因?yàn)檫@不僅能夠滿足世人獵奇的心態(tài),還能最大程度地警示所有的香港人、尤其是香港女人——在這個(gè)國(guó)際自由貿(mào)易港,這個(gè)頂級(jí)消費(fèi)社會(huì),貧窮是可恥的,階層墜落更是不可饒恕的。
2021年3月,布蘭妮·斯皮爾斯在社交媒體上發(fā)了一段跳舞的視頻,配文提到,在《陷害布蘭妮》上線后,她足足“哭了兩個(gè)星期”,“我的人生總是被猜測(cè)、被注視、被審判。為了我的精神狀態(tài)(保持理智和穩(wěn)定),我需要在每個(gè)晚上跳舞,來感受野性地、作為一個(gè)人似的活著。我的整個(gè)人生都被表演在公眾面前。將脆弱托付給世界、展現(xiàn)給世界,真的需要很大的力量,因?yàn)槲铱偸潜粚徟小⒈晃耆瑁幻襟w搞得狼狽不堪,直到今天依然如此。”
在《新周刊》的記者眼中,紀(jì)錄片《陷害布蘭妮》是對(duì)過去傷害布蘭妮的小報(bào)的一次清算,人們希望借此將這位昔日美國(guó)偶像從疑似泥沼般的生活里解救出來。
但很難判斷,布蘭妮自己是否需要媒體的再一次巨大關(guān)注。對(duì)她而言,《陷害布蘭妮》到底是對(duì)她的拯救,還是再一次揭開她的疤痕?就像粉絲發(fā)起的“釋放布蘭妮”運(yùn)動(dòng),通過布蘭妮社交媒體上的蛛絲馬跡來解讀她的生活狀態(tài),從而得出她需要“被拯救”的結(jié)論,這一方面讓更多粉絲聯(lián)合起來,給予布蘭妮支持和力量,另一方面,也讓布蘭妮的隱私和傷痛暴露于公眾面前。
紀(jì)錄片《陷害布蘭妮》截圖。
“世界根本無權(quán)知道有關(guān)布蘭妮·斯皮爾斯的任何事情。但是,大眾和媒體卻喜歡對(duì)女性公眾人物生活中最私密的細(xì)節(jié)進(jìn)行關(guān)注和判斷。每一個(gè)粉絲、記者和媒體評(píng)論員都認(rèn)為自己有權(quán)了解和評(píng)判斯皮爾斯最私人的事情。這是一種令人不安的侵犯,和監(jiān)護(hù)本身帶來的控制似乎并沒有什么不同,”霍夫斯特拉大學(xué)(Hofstra University)公共關(guān)系副教授卡拉·阿拉莫(Kara Alaimo)發(fā)表在CNN的評(píng)論中寫道,“監(jiān)護(hù)是否必要,是圍繞布蘭妮·斯皮爾斯的心理健康和財(cái)務(wù)狀況展開的。這些都是她有權(quán)保密的問題。在美國(guó),關(guān)于這方面的隱私權(quán)規(guī)定非常明確。《紐約時(shí)報(bào)》和其他新聞媒體的報(bào)道并不違法,但很難把挖掘女性私生活理解為為了什么公共利益。”
這意味著,在屏幕之前審視、關(guān)注甚至支持著布蘭妮的人們也并非全然置身事外,以至于在某種程度上成為了悲劇的幫兇。公眾號(hào)“看理想”則進(jìn)一步提出,這種審視不僅傷害了布蘭妮,也可能在傷害我們,在觀看明星被規(guī)訓(xùn)的同時(shí),我們自身也成了規(guī)訓(xùn)的對(duì)象——
如果身為“監(jiān)視主體”的我們,總是在以自我感覺良好的傲慢態(tài)度和侵略性眼光來檢視著這些明星的話,也會(huì)在不知不覺間通過自我規(guī)訓(xùn)的方式,默然順從了偏見、刻板和節(jié)目所宣揚(yáng)的美學(xué)標(biāo)準(zhǔn)與行為規(guī)范。——想要安全逃脫這種“全景敞視機(jī)制”(panopticism)的控制,是絕無可能的。
參考鏈接:
1.紀(jì)錄片《陷害布蘭妮》(Framing Britney Spears),《紐約時(shí)報(bào)》團(tuán)隊(duì)拍攝
2.https://www.forbes.com/sites/maddieberg/2021/06/23/britney-spears-full-statement-against-her-conservatorship/?sh=4cb8fb0421bd
3.https://www.npr.org/2021/06/24/1009726455/britney-spears-conservatorship-how-thats-supposed-to-work
4.https://www.nytimes.com/2021/07/01/us/politics/britney-spears-warren-casey-conservatorship.html
5.https://www.foxnews.com/entertainment/free-britney-a-bipartisan-cause-on-capitol-hill-after-impassioned-testimony
6.https://www.hrw.org/news/2021/06/26/britney-spearss-conservatorship-mirrors-reality-millions-disabilities
7.https://www.cnn.com/2021/06/23/opinions/britney-spears-deserves-privacy-alaimo/index.html
8. https://www.nytimes.com/2021/06/29/opinion/britney-spears-conservatorship.html
9.https://www.washingtonpost.com/opinions/2021/06/26/britney-spears-conservatorship-mistreatment/
10.《是誰陷害了“小甜甜”布蘭妮?》,看理想,2021-02-25,https://mp.weixin.qq.com/s/Cg02JcHn4uOpKWqEEhosKA
11.《從小甜甜到“瘋女人”,誰在陷害布蘭妮?》,新周刊,2021-05-02,https://mp.weixin.qq.com/s/dhFRiUXByvhgvwCqFTcPrA
12.《誰制造了小甜甜布蘭妮的悲劇?》,李孟蘇,三聯(lián)生活周刊,2021-03-10,https://mp.weixin.qq.com/s/G4_VB4xBSSTzET-qYsqgFw
13.《香港為什么有那么多“瘋女人”?》,安小慶,每日人物,2018-08-19,https://mp.weixin.qq.com/s/K5fK_TIkH5VrTClPjBMG8g
撰文 | 肖舒妍
編輯 | 李永博;王青
校對(duì) | 王心
端開發(fā)者丨JavaScript
實(shí)際需求中開始
要求:
此類繼承自 Date,擁有Date的所有屬性和對(duì)象
此類可以自由拓展方法
形象點(diǎn)描述,就是要求可以這樣:
// 假設(shè)最終的類是 MyDate,有一個(gè)getTest拓展方法
let date=newMyDate();
// 調(diào)用Date的方法,輸出GMT絕對(duì)毫秒數(shù)
console.log(date.getTime());
// 調(diào)用拓展的方法,隨便輸出什么,譬如helloworld!
console.log(date.getTest());
于是,隨手用JS中經(jīng)典的組合寄生法寫了一個(gè)繼承,然后,剛準(zhǔn)備完美收工,一運(yùn)行,卻出現(xiàn)了以下的情景:
但是的心情是這樣的: 囧
以前也沒有遇到過類似的問題,然后自己嘗試著用其它方法,多次嘗試,均無果(不算暴力混合法的情況),其實(shí)回過頭來看,是因?yàn)樗悸沸缕妫瑧{空想不到,并不是原理上有多難。。。
于是,借助強(qiáng)大的搜素引擎,搜集資料,最后,再自己總結(jié)了一番,才有了本文。
正文開始前,各位看官可以先暫停往下讀,嘗試下,在不借助任何網(wǎng)絡(luò)資料的情況下,是否能實(shí)現(xiàn)上面的需求?(就以 10分鐘為限吧)
借助stackoverflow上的回答。
先看看本文最開始時(shí)提到的經(jīng)典繼承法實(shí)現(xiàn),如下:
/**
* 經(jīng)典的js組合寄生繼承
*/
functionMyDate() {
Date.apply(this, arguments);
this.abc=1;
}
functioninherits(subClass, superClass) {
functionInner() {}
Inner.prototype=superClass.prototype;
subClass.prototype=newInner();
subClass.prototype.constructor=subClass;
}
inherits(MyDate,Date);
MyDate.prototype.getTest=function() {
returnthis.getTime();
};
let date=newMyDate();
console.log(date.getTest());
就是這段代碼?,這也是JavaScript高程(紅寶書)中推薦的一種,一直用,從未失手,結(jié)果現(xiàn)在馬失前蹄。。。
我們?cè)倩仡櫹滤膱?bào)錯(cuò):
再打印它的原型看看:
怎么看都沒問題,因?yàn)榘凑赵玩溁厮菀?guī)則, Date的所有原型方法都可以通過 MyDate對(duì)象的原型鏈往上回溯到。再仔細(xì)看看,發(fā)現(xiàn)它的關(guān)鍵并不是找不到方法,而是 thisisnotaDateobject.
嗯哼,也就是說,關(guān)鍵是:由于調(diào)用的對(duì)象不是Date的實(shí)例,所以不允許調(diào)用,就算是自己通過原型繼承的也不行。
首先,看看 MDN上的解釋,上面有提到,JavaScript的日期對(duì)象只能通過 JavaScriptDate作為構(gòu)造函數(shù)來實(shí)例化。
然后再看看stackoverflow上的回答:
有提到, v8引擎底層代碼中有限制,如果調(diào)用對(duì)象的 [[Class]]不是 Date,則拋出錯(cuò)誤。
總的來說,結(jié)合這兩點(diǎn),可以得出一個(gè)結(jié)論:要調(diào)用Date上方法的實(shí)例對(duì)象必須通過Date構(gòu)造出來,否則不允許調(diào)用Date的方法。
雖然原因找到了,但是問題仍然要解決啊,真的就沒辦法了么?當(dāng)然不是,事實(shí)上還是有不少實(shí)現(xiàn)的方法的。
首先,說說說下暴力的混合法,它是下面這樣子的:
說到底就是:內(nèi)部生成一個(gè) Date對(duì)象,然后此類暴露的方法中,把原有 Date中所有的方法都代理一遍,而且嚴(yán)格來說,這根本算不上繼承(都沒有原型鏈回溯)。
然后,再看看ES5中如何實(shí)現(xiàn)?
// 需要考慮polyfill情況
Object.setPrototypeOf=Object.setPrototypeOf ||
function(obj, proto) {
obj.__proto__=proto;
returnobj;
};
/**
* 用了點(diǎn)技巧的繼承,實(shí)際上返回的是Date對(duì)象
*/
functionMyDate() {
// bind屬于Function.prototype,接收的參數(shù)是:object, param1, params2...
vardateInst=new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();
// 更改原型指向,否則無法調(diào)用MyDate原型上的方法
// ES6方案中,這里就是[[prototype]]這個(gè)隱式原型對(duì)象,在沒有標(biāo)準(zhǔn)以前就是__proto__
Object.setPrototypeOf(dateInst,MyDate.prototype);
dateInst.abc=1;
returndateInst;
}
// 原型重新指回Date,否則根本無法算是繼承
Object.setPrototypeOf(MyDate.prototype,Date.prototype);
MyDate.prototype.getTest=functiongetTest() {
returnthis.getTime();
};
let date=newMyDate();
// 正常輸出,譬如1515638988725
console.log(date.getTest());
一眼看上去不知所措?沒關(guān)系,先看下圖來理解:(原型鏈關(guān)系一目了然)
可以看到,用的是非常巧妙的一種做法:
正常繼承的情況如下:
newMyDate()返回實(shí)例對(duì)象 date是由 MyDate構(gòu)造的
原型鏈回溯是: date(MyDate對(duì)象)-date.__proto__-MyDate.prototype-MyDate.prototype.__proto__-Date.prototype
這種做法的繼承的情況如下:
newMyDate()返回實(shí)例對(duì)象 date是由 Date構(gòu)造的
原型鏈回溯是: date(Date對(duì)象)-date.__proto__-MyDate.prototype-MyDate.prototype.__proto__-Date.prototype
可以看出,關(guān)鍵點(diǎn)在于:
構(gòu)造函數(shù)里返回了一個(gè)真正的 Date對(duì)象(由 Date構(gòu)造,所以有這些內(nèi)部類中的關(guān)鍵 [[Class]]標(biāo)志),所以它有調(diào)用 Date原型上方法的權(quán)利
構(gòu)造函數(shù)里的Date對(duì)象的 [[ptototype]](對(duì)外,瀏覽器中可通過 __proto__訪問)指向 MyDate.prototype,然后 MyDate.prototype再指向 Date.prototype。
所以最終的實(shí)例對(duì)象仍然能進(jìn)行正常的原型鏈回溯,回溯到原本Date的所有原型方法。
這樣通過一個(gè)巧妙的欺騙技巧,就實(shí)現(xiàn)了完美的Date繼承。不過補(bǔ)充一點(diǎn), MDN上有提到盡量不要修改對(duì)象的 [[Prototype]],因?yàn)檫@樣可能會(huì)干涉到瀏覽器本身的優(yōu)化。如果你關(guān)心性能,你就不應(yīng)該在一個(gè)對(duì)象中修改它的 [[Prototype]]
當(dāng)然,除了上述的ES5實(shí)現(xiàn),ES6中也可以直接繼承(自帶支持繼承 Date),而且更為簡(jiǎn)單:
classMyDateextendsDate{
constructor() {
super();
this.abc=1;
}
getTest() {
returnthis.getTime();
}
}
let date=newMyDate();
// 正常輸出,譬如1515638988725
console.log(date.getTest());
對(duì)比下ES5中的實(shí)現(xiàn),這個(gè)真的是簡(jiǎn)單的不行,直接使用ES6的Class語法就行了。而且,也可以正常輸出。
注意:這里的正常輸出環(huán)境是直接用ES6運(yùn)行,不經(jīng)過babel打包,打包后實(shí)質(zhì)上是轉(zhuǎn)化成ES5的,所以效果完全不一樣。
雖然說上述ES6大法是可以直接繼承Date的,但是,考慮到實(shí)質(zhì)上大部分的生產(chǎn)環(huán)境是: ES6+Babel
直接這樣用ES6 + Babel是會(huì)出問題的。
不信的話,可以自行嘗試下,Babel打包成ES5后代碼大致是這樣的:
然后當(dāng)信心滿滿的開始用時(shí),會(huì)發(fā)現(xiàn):
對(duì),又出現(xiàn)了這個(gè)問題,也許這時(shí)候是這樣的⊙?⊙
因?yàn)檗D(zhuǎn)譯后的ES5源碼中,仍然是通過 MyDate來構(gòu)造,而 MyDate的構(gòu)造中又無法修改屬于 Date內(nèi)部的 [[Class]]之類的私有標(biāo)志,因此構(gòu)造出的對(duì)象仍然不允許調(diào)用 Date方法(調(diào)用時(shí),被引擎底層代碼識(shí)別為 [[Class]]標(biāo)志不符合,不允許調(diào)用,拋出錯(cuò)誤)。
由此可見,ES6繼承的內(nèi)部實(shí)現(xiàn)和Babel打包編譯出來的實(shí)現(xiàn)是有區(qū)別的。(雖說Babel的polyfill一般會(huì)按照定義的規(guī)范去實(shí)現(xiàn)的,但也不要過度迷信)。
雖然上述提到的三種方法都可以達(dá)到繼承 Date的目的-混合法嚴(yán)格說不能算繼承,只不過是另類實(shí)現(xiàn)。
于是,將所有能打印的主要信息都打印出來,分析幾種繼承的區(qū)別,大致場(chǎng)景是這樣的:
可以參考:( 請(qǐng)進(jìn)入調(diào)試模式)https://dailc.github.io/fe-interview/demo/extends_date.html
從上往下, 1,2,3,4四種繼承實(shí)現(xiàn)分別是:(排出了混合法)
ES6的Class大法
經(jīng)典組合寄生繼承法
本文中的取巧做法,Date構(gòu)造實(shí)例,然后更改 __proto__的那種
ES6的Class大法,Babel打包后的實(shí)現(xiàn)(無法正常調(diào)用的)
~~~~以下是MyDate們的prototype~~~~~~~~~
Date{constructor: ?, getTest: ?}
Date{constructor: ?, getTest: ?}
Date{getTest: ?, constructor: ?}
Date{constructor: ?, getTest: ?}
~~~~以下是new出的對(duì)象~~~~~~~~~
SatJan13201821:58:55GMT+0800(CST)
MyDate2{abc:1}
SatJan13201821:58:55GMT+0800(CST)
MyDate{abc:1}
~~~~以下是new出的對(duì)象的Object.prototype.toString.call~~~~~~~~~
[objectDate]
[objectObject]
[objectDate]
[objectObject]
~~~~以下是MyDate們的__proto__~~~~~~~~~
?Date() { [native code] }
? () { [native code] }
? () { [native code] }
?Date() { [native code] }
~~~~以下是new出的對(duì)象的__proto__~~~~~~~~~
Date{constructor: ?, getTest: ?}
Date{constructor: ?, getTest: ?}
Date{getTest: ?, constructor: ?}
Date{constructor: ?, getTest: ?}
~~~~以下是對(duì)象的__proto__與MyDate們的prototype比較~~~~~~~~~
true
true
true
true
看出,主要差別有幾點(diǎn):
MyDate們的proto指向不一樣
Object.prototype.toString.call的輸出不一樣
對(duì)象本質(zhì)不一樣,可以正常調(diào)用的 1,3都是 Date構(gòu)造出的,而其它的則是 MyDate構(gòu)造出的
我們上文中得出的一個(gè)結(jié)論是:由于調(diào)用的對(duì)象不是由Date構(gòu)造出的實(shí)例,所以不允許調(diào)用,就算是自己的原型鏈上有Date.prototype也不行
但是這里有兩個(gè)變量:分別是底層構(gòu)造實(shí)例的方法不一樣,以及對(duì)象的 Object.prototype.toString.call的輸出不一樣(另一個(gè) MyDate.__proto__可以排除,因?yàn)樵玩溁厮菘隙ㄅc它無關(guān))。
萬一它的判斷是根據(jù) Object.prototype.toString.call來的呢?那這樣結(jié)論不就有誤差了?
于是,根據(jù)ES6中的, Symbol.toStringTag,使用黑魔法,動(dòng)態(tài)的修改下它,排除下干擾:
// 分別可以給date2,date3設(shè)置
Object.defineProperty(date2,Symbol.toStringTag, {
get:function() {
returnDate;
}
});
然后在打印下看看,變成這樣了:
[objectDate]
[objectDate]
[objectDate]
[objectObject]
可以看到,第二個(gè)的 MyDate2構(gòu)造出的實(shí)例,雖然打印出來是 [objectDate],但是調(diào)用Date方法仍然是有錯(cuò)誤。
此時(shí)我們可以更加準(zhǔn)確一點(diǎn)的確認(rèn):由于調(diào)用的對(duì)象不是由Date構(gòu)造出的實(shí)例,所以不允許調(diào)用。
而且我們可以看到,就算通過黑魔法修改 Object.prototype.toString.call,內(nèi)部的 [[Class]]標(biāo)識(shí)位也是無法修改的。(這塊知識(shí)點(diǎn)大概是Object.prototype.toString.call可以輸出內(nèi)部的[[Class]],但無法改變它,由于不是重點(diǎn),這里不贅述)。
從上午中的分析可以看到一點(diǎn):ES6的Class寫法繼承是沒問題的。但是換成ES5寫法就不行了。
所以ES6的繼承大法和ES5肯定是有區(qū)別的,那么究竟是哪里不同呢?(主要是結(jié)合的本文繼承Date來說)
區(qū)別:(以 SubClass, SuperClass, instance為例)
ES5中繼承的實(shí)質(zhì)是:(那種經(jīng)典組合寄生繼承法)
先由子類( SubClass)構(gòu)造出實(shí)例對(duì)象this
然后在子類的構(gòu)造函數(shù)中,將父類( SuperClass)的屬性添加到 this上, SuperClass.apply(this,arguments)
子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)
所以 instance是子類( SubClass)構(gòu)造出的(所以沒有父類的 [[Class]]關(guān)鍵標(biāo)志)
所以, instance有 SubClass和 SuperClass的所有實(shí)例屬性,以及可以通過原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法
ES6中繼承的實(shí)質(zhì)是:
先由父類( SuperClass)構(gòu)造出實(shí)例對(duì)象this,這也是為什么必須先調(diào)用父類的 super()方法(子類沒有自己的this對(duì)象,需先由父類構(gòu)造)
然后在子類的構(gòu)造函數(shù)中,修改this(進(jìn)行加工),譬如讓它指向子類原型( SubClass.prototype),這一步很關(guān)鍵,否則無法找到子類原型(注,子類構(gòu)造中加工這一步的實(shí)際做法是推測(cè)出的,從最終效果來推測(cè))
然后同樣,子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)
所以 instance是父類( SuperClass)構(gòu)造出的(所以有著父類的 [[Class]]關(guān)鍵標(biāo)志)
所以, instance有 SubClass和 SuperClass的所有實(shí)例屬性,以及可以通過原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法
以上?就列舉了些重要信息,其它的如靜態(tài)方法的繼承沒有贅述。(靜態(tài)方法繼承實(shí)質(zhì)上只需要更改下 SubClass.__proto__到 SuperClass即可)
可以看著這張圖快速理解:
有沒有發(fā)現(xiàn)呢:ES6中的步驟和本文中取巧繼承Date的方法一模一樣,不同的是ES6是語言底層的做法,有它的底層優(yōu)化之處,而本文中的直接修改_proto_容易影響性能。
ES6中在super中構(gòu)建this的好處?
因?yàn)镋S6中允許我們繼承內(nèi)置的類,如Date,Array,Error等。如果this先被創(chuàng)建出來,在傳給Array等系統(tǒng)內(nèi)置類的構(gòu)造函數(shù),這些內(nèi)置類的構(gòu)造函數(shù)是不認(rèn)這個(gè)this的。所以需要現(xiàn)在super中構(gòu)建出來,這樣才能有著super中關(guān)鍵的 [[Class]]標(biāo)志,才能被允許調(diào)用。(否則就算繼承了,也無法調(diào)用這些內(nèi)置類的方法)
看到這里,不知道是否對(duì)上午中頻繁提到的構(gòu)造函數(shù),實(shí)例對(duì)象有所混淆與困惑呢?這里稍微描述下。
要弄懂這一點(diǎn),需要先知道 new一個(gè)對(duì)象到底發(fā)生了什么?先形象點(diǎn)說:
functionMyClass() {
this.abc=1;
}
MyClass.prototype.print=function() {
console.log('this.abc:'+this.abc);
};
let instance=newMyClass();
譬如,上述就是一個(gè)標(biāo)準(zhǔn)的實(shí)例對(duì)象生成,都發(fā)生了什么呢?
步驟簡(jiǎn)述如下:(參考MDN,還有部分關(guān)于底層的描述略去-如[[Class]]標(biāo)識(shí)位等)
構(gòu)造函數(shù)內(nèi)部,創(chuàng)建一個(gè)新的對(duì)象,它繼承自 MyClass.prototype, letinstance=Object.create(MyClass.prototype);
使用指定的參數(shù)調(diào)用構(gòu)造函數(shù) MyClass,并將 this綁定到新創(chuàng)建的對(duì)象, MyClass.call(instance);,執(zhí)行后擁有所有實(shí)例屬性
如果構(gòu)造函數(shù)返回了一個(gè)“對(duì)象”,那么這個(gè)對(duì)象會(huì)取代整個(gè) new出來的結(jié)果。如果構(gòu)造函數(shù)沒有返回對(duì)象,那么new出來的結(jié)果為步驟1創(chuàng)建的對(duì)象。 (一般情況下構(gòu)造函數(shù)不返回任何值,不過用戶如果想覆蓋這個(gè)返回值,可以自己選擇返回一個(gè)普通對(duì)象來覆蓋。當(dāng)然,返回?cái)?shù)組也會(huì)覆蓋,因?yàn)閿?shù)組也是對(duì)象。)
結(jié)合上述的描述,大概可以還原成以下代碼(簡(jiǎn)單還原,不考慮各種其它邏輯):
let instance=Object.create(MyClass.prototype);
let innerConstructReturn=MyClass.call(instance);
let innerConstructReturnIsObj=typeofinnerConstructReturn==='object'||typeofinnerConstructReturn==='function';
returninnerConstructReturnIsObj ? innerConstructReturn : instance;
注意?:普通的函數(shù)構(gòu)建,可以簡(jiǎn)單的認(rèn)為就是上述步驟。實(shí)際上對(duì)于一些內(nèi)置類(如Date等),并沒有這么簡(jiǎn)單,還有一些自己的隱藏邏輯,譬如 [[Class]]標(biāo)識(shí)位等一些重要私有屬性。譬如可以在MDN上看到,以常規(guī)函數(shù)調(diào)用Date(即不加 new 操作符)將會(huì)返回一個(gè)字符串,而不是一個(gè)日期對(duì)象,如果這樣模擬的話會(huì)無效。
覺得看起來比較繁瑣?可以看下圖梳理:
那現(xiàn)在再回頭看看。
什么是構(gòu)造函數(shù)?
如上述中的 MyClass就是一個(gè)構(gòu)造函數(shù),在內(nèi)部它構(gòu)造出了 instance對(duì)象。
什么是實(shí)例對(duì)象?
instance就是一個(gè)實(shí)例對(duì)象,它是通過 new出來的?
實(shí)例與構(gòu)造的關(guān)系
有時(shí)候淺顯點(diǎn),可以認(rèn)為構(gòu)造函數(shù)是xxx就是xxx的實(shí)例。即:
let instance=newMyClass();
此時(shí)我們就可以認(rèn)為 instance是 MyClass的實(shí)例,因?yàn)樗臉?gòu)造函數(shù)就是它。
不一定,我們那ES5黑魔法來做示例。
functionMyDate() {
// bind屬于Function.prototype,接收的參數(shù)是:object, param1, params2...
vardateInst=new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();
// 更改原型指向,否則無法調(diào)用MyDate原型上的方法
// ES6方案中,這里就是[[prototype]]這個(gè)隱式原型對(duì)象,在沒有標(biāo)準(zhǔn)以前就是__proto__
Object.setPrototypeOf(dateInst,MyDate.prototype);
dateInst.abc=1;
returndateInst;
}
我們可以看到 instance的最終指向的原型是 MyDate.prototype,而 MyDate.prototype的構(gòu)造函數(shù)是 MyDate,因此可以認(rèn)為 instance是 MyDate的實(shí)例。
但是,實(shí)際上, instance卻是由 Date構(gòu)造的,我們可以繼續(xù)用 ES6中的 new.target來驗(yàn)證。
注意?:關(guān)于 new.target, MDN中的定義是:new.target返回一個(gè)指向構(gòu)造方法或函數(shù)的引用。
嗯哼,也就是說,返回的是構(gòu)造函數(shù)。
我們可以在相應(yīng)的構(gòu)造中測(cè)試打印:
classMyDateextendsDate{
constructor() {
super();
this.abc=1;
console.log('~~~new.target.name:MyDate~~~~');
console.log(new.target.name);
}
}
// new操作時(shí)的打印結(jié)果是:
// ~~~new.target.name:MyDate~~~~
// MyDate
然后,可以在上面的示例中看到,就算是ES6的Class繼承, MyDate構(gòu)造中打印 new.target也顯示 MyDate,但實(shí)際上它是由 Date來構(gòu)造(有著 Date關(guān)鍵的 [[Class]]標(biāo)志,因?yàn)槿绻皇荄ate構(gòu)造(如沒有標(biāo)志)是無法調(diào)用Date的方法的)。
這也算是一次小小的勘誤吧。
所以,實(shí)際上用 new.target是無法判斷實(shí)例對(duì)象到底是由哪一個(gè)構(gòu)造構(gòu)造的(這里指的是判斷底層真正的 [[Class]]標(biāo)志來源的構(gòu)造)。
再回到結(jié)論:實(shí)例對(duì)象不一定就是由它的原型上的構(gòu)造函數(shù)構(gòu)造的,有可能構(gòu)造函數(shù)內(nèi)部有著寄生等邏輯,偷偷的用另一個(gè)函數(shù)來構(gòu)造了下,當(dāng)然,簡(jiǎn)單情況下,我們直接說實(shí)例對(duì)象由對(duì)應(yīng)構(gòu)造函數(shù)構(gòu)造也沒錯(cuò)(不過,在涉及到這種Date之類的分析時(shí),我們還是得明白)。
這一部分為補(bǔ)充內(nèi)容。
前文中一直提到一個(gè)概念:Date內(nèi)部的 [[Class]]標(biāo)識(shí)。
其實(shí),嚴(yán)格來說,不能這樣泛而稱之(前文中只是用這個(gè)概念是為了降低復(fù)雜度,便于理解),它可以分為以下兩部分:
在ES5中,每種內(nèi)置對(duì)象都定義了 [[Class]] 內(nèi)部屬性的值,[[Class]] 內(nèi)部屬性的值用于內(nèi)部區(qū)分對(duì)象的種類
Object.prototype.toString訪問的就是這個(gè)[[Class]]
規(guī)范中除了通過 Object.prototype.toString,沒有提供任何手段使程序訪問此值。
而且Object.prototype.toString輸出無法被修改
而在ES5中,之前的 [[Class]] 不再使用,取而代之的是一系列的 internalslot
Internal slot 對(duì)應(yīng)于與對(duì)象相關(guān)聯(lián)并由各種ECMAScript規(guī)范算法使用的內(nèi)部狀態(tài),它們沒有對(duì)象屬性,也不能被繼承
根據(jù)具體的 Internal slot 規(guī)范,這種狀態(tài)可以由任何ECMAScript語言類型或特定ECMAScript規(guī)范類型值的值組成
通過 Object.prototype.toString,仍然可以輸出Internal slot值
簡(jiǎn)單點(diǎn)理解(簡(jiǎn)化理解),Object.prototype.toString的流程是:如果是基本數(shù)據(jù)類型(除去Object以外的幾大類型),則返回原本的slot,如果是Object類型(包括內(nèi)置對(duì)象以及自己寫的對(duì)象),則調(diào)用 Symbol.toStringTag。 Symbol.toStringTag方法的默認(rèn)實(shí)現(xiàn)就是返回對(duì)象的Internal slot,這個(gè)方法可以被重寫
這兩點(diǎn)是有所差異的,需要區(qū)分(不過簡(jiǎn)單點(diǎn)可以統(tǒng)一理解為內(nèi)置對(duì)象內(nèi)部都有一個(gè)特殊標(biāo)識(shí),用來區(qū)分對(duì)應(yīng)類型-不符合類型就不給調(diào)用)。
JS內(nèi)置對(duì)象是這些:
Arguments,Array,Boolean,Date,Error,Function,JSON,Math,Number,Object,RegExp,String
ES6新增的一些,這里未提到:(如Promise對(duì)象可以輸出 [objectPromise]),而前文中提到的:
Object.defineProperty(date,Symbol.toStringTag, {
get:function() {
returnDate;
}
});
它的作用是重寫Symbol.toStringTag,截取date(雖然是內(nèi)置對(duì)象,但是仍然屬于Object)的 Object.prototype.toString的輸出,讓這個(gè)對(duì)象輸出自己修改后的 [objectDate]。
但是,僅僅是做到輸出的時(shí)候變成了Date,實(shí)際上內(nèi)部的 internalslot值并沒有被改變,因此仍然不被認(rèn)為是Date。
其實(shí),在判斷繼承時(shí),沒有那么多的技巧,就只有關(guān)鍵的一點(diǎn): [[prototype]]( __ptoto__)的指向關(guān)系。
譬如:
console.log(instanceinstanceofSubClass);
console.log(instanceinstanceofSuperClass);
實(shí)質(zhì)上就是:
SubClass.prototype是否出現(xiàn)在 instance的原型鏈上
SuperClass.prototype是否出現(xiàn)在 instance的原型鏈上
然后,對(duì)照本文中列舉的一些圖,一目了然就可以看清關(guān)系。有時(shí)候,完全沒有必要弄的太復(fù)雜。
覺得本文對(duì)你有幫助?請(qǐng)分享給更多人
前端開發(fā)者丨JavaScript
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。